From 10be819a16067cbc9520018eb6cb5acfe757eb4b Mon Sep 17 00:00:00 2001 From: Robert Marko Date: Tue, 30 May 2023 23:38:56 +0200 Subject: [PATCH 01/37] ipq807x: 6.1: fix I/O WRITE_ZEROES errors on some eMMC devices Linux 5.19 added a feature where if there is TRIM support being advertised on eMMC kernel will use TRIM to offload erasing to zero. However, like always there are eMMC IC-s that advertise TRIM and kind of work but trying to use TRIM for offloading will cause I/O errors like: [ 18.085950] I/O error, dev loop0, sector 596 op 0x9:(WRITE_ZEROES) flags 0x800 phys_seg 0 prio class 2 So, lets utilize the kernel MMC quirks DB to disable TRIM for eMMC models that are known to cause this. This will fix the WRITE_ZEROES error on: Qnap 301W which uses Micron MTFC4GACAJCN-1M Zyxel NBG7815 which uses Kingston EMMC04G-M627 Tested-By: Enrico Mioso # NBG7815 Signed-off-by: Robert Marko --- ...sable-TRIM-on-Micron-MTFC4GACAJCN-1M.patch | 36 ++++++++++++++++++ ...isable-TRIM-on-Kingston-EMMC04G-M627.patch | 38 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 target/linux/ipq807x/patches-6.1/0132-mmc-core-disable-TRIM-on-Micron-MTFC4GACAJCN-1M.patch create mode 100644 target/linux/ipq807x/patches-6.1/0133-mmc-core-disable-TRIM-on-Kingston-EMMC04G-M627.patch diff --git a/target/linux/ipq807x/patches-6.1/0132-mmc-core-disable-TRIM-on-Micron-MTFC4GACAJCN-1M.patch b/target/linux/ipq807x/patches-6.1/0132-mmc-core-disable-TRIM-on-Micron-MTFC4GACAJCN-1M.patch new file mode 100644 index 0000000000..8c2e59eeb4 --- /dev/null +++ b/target/linux/ipq807x/patches-6.1/0132-mmc-core-disable-TRIM-on-Micron-MTFC4GACAJCN-1M.patch @@ -0,0 +1,36 @@ +From f5aaf6669bd4f1f0218dd7fd5dc90941267ad860 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Tue, 30 May 2023 23:26:30 +0200 +Subject: [PATCH] mmc: core: disable TRIM on Micron MTFC4GACAJCN-1M + +It seems that Micron MTFC4GACAJCN-1M despite advertising TRIM support does +not work when the core is trying to use REQ_OP_WRITE_ZEROES. + +We are seeing the following errors in OpenWrt under 6.1 on Qnap Qhora 301W +that we did not previously have and tracked it down to REQ_OP_WRITE_ZEROES: +[ 18.085950] I/O error, dev loop0, sector 596 op 0x9:(WRITE_ZEROES) flags 0x800 phys_seg 0 prio class 2 + +Disabling TRIM makes the error go away, so lets add a quirk for this eMMC +to disable TRIM. + +Signed-off-by: Robert Marko +--- + drivers/mmc/core/quirks.h | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/drivers/mmc/core/quirks.h ++++ b/drivers/mmc/core/quirks.h +@@ -101,6 +101,13 @@ static const struct mmc_fixup __maybe_un + MMC_QUIRK_TRIM_BROKEN), + + /* ++ * Micron MTFC4GACAJCN-1M advertises TRIM but it does not seems to ++ * support being used to offload WRITE_ZEROES. ++ */ ++ MMC_FIXUP("Q2J54A", CID_MANFID_MICRON, 0x014e, add_quirk_mmc, ++ MMC_QUIRK_TRIM_BROKEN), ++ ++ /* + * Some SD cards reports discard support while they don't + */ + MMC_FIXUP(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, add_quirk_sd, diff --git a/target/linux/ipq807x/patches-6.1/0133-mmc-core-disable-TRIM-on-Kingston-EMMC04G-M627.patch b/target/linux/ipq807x/patches-6.1/0133-mmc-core-disable-TRIM-on-Kingston-EMMC04G-M627.patch new file mode 100644 index 0000000000..ac7af52a74 --- /dev/null +++ b/target/linux/ipq807x/patches-6.1/0133-mmc-core-disable-TRIM-on-Kingston-EMMC04G-M627.patch @@ -0,0 +1,38 @@ +From 26c97b6fb7d291f55e0e4a410d266d1355118ed9 Mon Sep 17 00:00:00 2001 +From: Robert Marko +Date: Wed, 31 May 2023 20:21:26 +0200 +Subject: [PATCH] mmc: core: disable TRIM on Kingston EMMC04G-M627 + +It seems that Kingston EMMC04G-M627 despite advertising TRIM support does +not work when the core is trying to use REQ_OP_WRITE_ZEROES. + +We are seeing I/O errors in OpenWrt under 6.1 on Zyxel NBG7815 that we did +not previously have and tracked it down to REQ_OP_WRITE_ZEROES. + +Trying to use fstrim seems to also throw errors like: +[93010.835112] I/O error, dev loop0, sector 16902 op 0x3:(DISCARD) flags 0x800 phys_seg 1 prio class 2 + +Disabling TRIM makes the error go away, so lets add a quirk for this eMMC +to disable TRIM. + +Signed-off-by: Robert Marko +--- + drivers/mmc/core/quirks.h | 7 +++++++ + 1 file changed, 7 insertions(+) + +--- a/drivers/mmc/core/quirks.h ++++ b/drivers/mmc/core/quirks.h +@@ -108,6 +108,13 @@ static const struct mmc_fixup __maybe_un + MMC_QUIRK_TRIM_BROKEN), + + /* ++ * Kingston EMMC04G-M627 advertises TRIM but it does not seems to ++ * support being used to offload WRITE_ZEROES. ++ */ ++ MMC_FIXUP("M62704", CID_MANFID_KINGSTON, 0x0100, add_quirk_mmc, ++ MMC_QUIRK_TRIM_BROKEN), ++ ++ /* + * Some SD cards reports discard support while they don't + */ + MMC_FIXUP(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, add_quirk_sd, From 218deba503f38e2f44f5012baf96af91b3e00c6a Mon Sep 17 00:00:00 2001 From: Christian Marangi Date: Wed, 31 May 2023 17:40:11 +0200 Subject: [PATCH 02/37] CI: label-kernel: support compile testing kernel version and all target Add support to label-kernel for compiling testing kernel version and check patches. To trigger this special build appent :testing to the normal label. Example: - ci:kernel:ipq806x:generic:testing Test will fail if the requested target doesn't have a defined kernel testing version. Also add support for testing all target and subtarget. To trigger this some special pattern are added: - ci:kernel:all:all Trigger test for all target and subtarget - ci:kernel:all:first Trigger test for all target and the first subtarget in alphabetical order for the target. With these special case :testing can also be used and every target and subtarget that supports kernel testing version will be selected: - ci:kernel:all:all:testing Trigger test for all target and subtarget that have a kernel testing version defined. - ci:kernel:all:first:testing Trigger test for all target and the first subtarget in alphabetical order for the target that, if they have a kernel testing version defined. Signed-off-by: Christian Marangi --- .github/workflows/label-kernel.yml | 97 +++++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 10 deletions(-) diff --git a/.github/workflows/label-kernel.yml b/.github/workflows/label-kernel.yml index 29a9d79558..eab79cf0c0 100644 --- a/.github/workflows/label-kernel.yml +++ b/.github/workflows/label-kernel.yml @@ -12,17 +12,84 @@ jobs: name: Set target runs-on: ubuntu-latest outputs: - target: ${{ steps.set_target.outputs.target }} - subtarget: ${{ steps.set_target.outputs.subtarget }} + targets_subtargets: ${{ steps.set_target.outputs.targets_subtargets }} + targets: ${{ steps.set_target.outputs.targets }} steps: - - name: Set target - id: set_target + - name: Checkout + uses: actions/checkout@v3 + + - name: Parse label + id: parse_label env: CI_EVENT_LABEL_NAME: ${{ github.event.label.name }} run: | - echo "$CI_EVENT_LABEL_NAME" | sed -n 's/.*:\(.*\):\(.*\)$/target=\1/p' | tee --append $GITHUB_OUTPUT - echo "$CI_EVENT_LABEL_NAME" | sed -n 's/.*:\(.*\):\(.*\)$/subtarget=\2/p' | tee --append $GITHUB_OUTPUT + echo "$CI_EVENT_LABEL_NAME" | sed -n 's/ci:kernel:\([^:]*\):\([^:]*\):*\([^:]*\)$/target=\1/p' | tee --append $GITHUB_OUTPUT + echo "$CI_EVENT_LABEL_NAME" | sed -n 's/ci:kernel:\([^:]*\):\([^:]*\):*\([^:]*\)$/subtarget=\2/p' | tee --append $GITHUB_OUTPUT + echo "$CI_EVENT_LABEL_NAME" | sed -n 's/ci:kernel:\([^:]*\):\([^:]*\):*\([^:]*\)$/testing=\3/p' | tee --append $GITHUB_OUTPUT + + - name: Set targets + id: set_target + run: | + ALL_TARGETS="$(perl ./scripts/dump-target-info.pl kernels 2>/dev/null)" + + TARGETS_SUBTARGETS="$(echo "$ALL_TARGETS" | sort -u -t '/' -k1)" + TARGETS="$(echo "$ALL_TARGETS" | sort -u -t '/' -k1,1)" + + [ "${{ steps.parse_label.outputs.subtarget }}" = "first" ] && TARGETS_SUBTARGETS=$TARGETS + + JSON_TARGETS_SUBTARGETS='[' + FIRST=1 + while IFS= read -r line; do + TARGET_SUBTARGET=$(echo $line | cut -d " " -f 1) + TARGET=$(echo $TARGET_SUBTARGET | cut -d "/" -f 1) + SUBTARGET=$(echo $TARGET_SUBTARGET | cut -d "/" -f 2) + + [ "${{ steps.parse_label.outputs.target }}" != "all" ] && [ "${{ steps.parse_label.outputs.target }}" != "$TARGET" ] && continue + [ "${{ steps.parse_label.outputs.subtarget }}" != "all" ] && [ "${{ steps.parse_label.outputs.subtarget }}" != "first" ] && + [ "${{ steps.parse_label.outputs.subtarget }}" != $SUBTARGET ] && continue + if [ "${{ steps.parse_label.outputs.testing }}" = "testing" ]; then + TESTING_KERNEL_VER=$(echo $line | cut -d " " -f 3) + [ -z "$TESTING_KERNEL_VER" ] && continue + fi + + TUPLE='{"target":"'"$TARGET"'","subtarget":"'"$SUBTARGET"'","testing":"'"$TESTING_KERNEL_VER"'"}' + [[ $FIRST -ne 1 ]] && JSON_TARGETS_SUBTARGETS="$JSON_TARGETS_SUBTARGETS"',' + JSON_TARGETS_SUBTARGETS="$JSON_TARGETS_SUBTARGETS""$TUPLE" + FIRST=0 + done <<< "$TARGETS_SUBTARGETS" + JSON_TARGETS_SUBTARGETS="$JSON_TARGETS_SUBTARGETS"']' + + JSON_TARGETS='[' + FIRST=1 + while IFS= read -r line; do + TARGET_SUBTARGET=$(echo $line | cut -d " " -f 1) + TARGET=$(echo $TARGET_SUBTARGET | cut -d "/" -f 1) + SUBTARGET=$(echo $TARGET_SUBTARGET | cut -d "/" -f 2) + + [ "${{ steps.parse_label.outputs.target }}" != "all" ] && [ "${{ steps.parse_label.outputs.target }}" != $TARGET ] && continue + if [ "${{ steps.parse_label.outputs.testing }}" = "testing" ]; then + TESTING_KERNEL_VER=$(echo $line | cut -d " " -f 3) + [ -z "$TESTING_KERNEL_VER" ] && continue + fi + + TUPLE='{"target":"'"$TARGET"'","subtarget":"'"$SUBTARGET"'","testing":"'"$TESTING_KERNEL_VER"'"}' + [[ $FIRST -ne 1 ]] && JSON_TARGETS="$JSON_TARGETS"',' + JSON_TARGETS="$JSON_TARGETS""$TUPLE" + FIRST=0 + done <<< "$TARGETS" + JSON_TARGETS="$JSON_TARGETS"']' + + echo -e "\n---- targets to build ----\n" + echo "$JSON_TARGETS_SUBTARGETS" + echo -e "\n---- targets to build ----\n" + + echo -e "\n---- targets to check patch ----\n" + echo "$JSON_TARGETS" + echo -e "\n---- targets to check patch ----\n" + + echo "targets_subtargets=$JSON_TARGETS_SUBTARGETS" >> $GITHUB_OUTPUT + echo "targets=$JSON_TARGETS" >> $GITHUB_OUTPUT build_kernel: name: Build Kernel with external toolchain @@ -32,10 +99,15 @@ jobs: packages: read actions: write uses: ./.github/workflows/build.yml + strategy: + fail-fast: False + matrix: + include: ${{fromJson(needs.set_target.outputs.targets_subtargets)}} with: container_name: toolchain - target: ${{ needs.set_target.outputs.target }} - subtarget: ${{ needs.set_target.outputs.subtarget }} + target: ${{ matrix.target }} + subtarget: ${{ matrix.subtarget }} + testing: ${{ matrix.testing != '' && true }} build_kernel: true build_all_kmods: true @@ -46,7 +118,12 @@ jobs: contents: read packages: read actions: write + strategy: + fail-fast: False + matrix: + include: ${{fromJson(needs.set_target.outputs.targets)}} uses: ./.github/workflows/check-kernel-patches.yml with: - target: ${{ needs.set_target.outputs.target }} - subtarget: ${{ needs.set_target.outputs.subtarget }} + target: ${{ matrix.target }} + subtarget: ${{ matrix.subtarget }} + testing: ${{ matrix.testing != '' && true }} From 93147443502e61d0a824406bef13b0b9fe250f71 Mon Sep 17 00:00:00 2001 From: Yanase Yuki Date: Wed, 31 May 2023 16:28:31 +0900 Subject: [PATCH 03/37] ipq806x: use new package name for NEC WG2600HP3 commit 0c45ad41e15e2255 changes ipq806x usb kmod name from usb-phy-qcom-dwc3 to phy-qcom-ipq806x-usb, so use new name. Signed-off-by: Yanase Yuki --- target/linux/ipq806x/image/generic.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/linux/ipq806x/image/generic.mk b/target/linux/ipq806x/image/generic.mk index 7ed2b80847..a3e9eb59e7 100644 --- a/target/linux/ipq806x/image/generic.mk +++ b/target/linux/ipq806x/image/generic.mk @@ -245,7 +245,7 @@ define Device/nec_wg2600hp3 pad-rootfs | append-metadata DEVICE_PACKAGES := -kmod-ata-ahci -kmod-ata-ahci-platform \ -kmod-usb-ohci -kmod-usb2 -kmod-usb-ledtrig-usbport \ - -kmod-usb-phy-qcom-dwc3 -kmod-usb3 -kmod-usb-dwc3-qcom \ + -kmod-phy-qcom-ipq806x-usb -kmod-usb3 -kmod-usb-dwc3-qcom \ ath10k-firmware-qca9984-ct endef TARGET_DEVICES += nec_wg2600hp3 From 9c1b3966150f9340b169f5adc1bf80dc1cfa54e0 Mon Sep 17 00:00:00 2001 From: Christian Lamparter Date: Wed, 31 May 2023 14:15:56 +0200 Subject: [PATCH 04/37] apm821xx: put crypto4xx into crypto subsection module is only useful for apm821xx targets, so limit visability to just this target. Fixes: 55fbcad20a2d ("apm821xx: make crypto4xx as a standalone module") Signed-off-by: Christian Lamparter --- target/linux/apm821xx/modules.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/target/linux/apm821xx/modules.mk b/target/linux/apm821xx/modules.mk index b837461157..f8880d6712 100644 --- a/target/linux/apm821xx/modules.mk +++ b/target/linux/apm821xx/modules.mk @@ -19,11 +19,12 @@ define KernelPackage/hw-crypto-4xx CONFIG_HW_RANDOM=y \ CONFIG_CRYPTO_DEV_PPC4XX \ CONFIG_HW_RANDOM_PPC4XX=y - DEPENDS:=+kmod-random-core +kmod-crypto-manager \ + DEPENDS:=@TARGET_apm821xx +kmod-random-core +kmod-crypto-manager \ +kmod-crypto-ccm +kmod-crypto-gcm \ +kmod-crypto-sha1 +kmod-crypto-sha256 +kmod-crypto-sha512 FILES:=$(LINUX_DIR)/drivers/crypto/amcc/crypto4xx.ko AUTOLOAD:=$(call AutoLoad,09,sata_dwc_460ex,1) + $(call AddDepends/crypto) endef define KernelPackage/hw-crypto-4xx/description From 33abdc07fbc1e6fb2e0d946187ff88c5270d76a6 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 31 May 2023 23:21:07 +0200 Subject: [PATCH 05/37] kernel: Package the new FOTG210 module properly When using the Gemini, we apply patches that create a single module that support both host and device mode these days. Signed-off-by: Linus Walleij (move module to gemini target, keep both 6.1+2-ish + 5.15 module CONFIG and files around until 5.15 is dropped) Signed-off-by: Christian Lamparter --- package/kernel/linux/modules/usb.mk | 11 ----------- target/linux/gemini/modules.mk | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 target/linux/gemini/modules.mk diff --git a/package/kernel/linux/modules/usb.mk b/package/kernel/linux/modules/usb.mk index 721e17f970..644365ed19 100644 --- a/package/kernel/linux/modules/usb.mk +++ b/package/kernel/linux/modules/usb.mk @@ -331,17 +331,6 @@ define KernelPackage/usb-bcma endef $(eval $(call KernelPackage,usb-bcma)) -define KernelPackage/usb-fotg210 - TITLE:=Support for FOTG210 USB host controllers - DEPENDS:=@USB_SUPPORT @TARGET_gemini - KCONFIG:=CONFIG_USB_FOTG210_HCD - FILES:= \ - $(if $(CONFIG_USB_FOTG210_HCD),$(LINUX_DIR)/drivers/usb/host/fotg210-hcd.ko) - AUTOLOAD:=$(call AutoLoad,50,fotg210-hcd,1) - $(call AddDepends/usb) -endef -$(eval $(call KernelPackage,usb-fotg210)) - define KernelPackage/usb-ssb TITLE:=Support for SSB USB controllers DEPENDS:=@USB_SUPPORT @TARGET_bcm47xx diff --git a/target/linux/gemini/modules.mk b/target/linux/gemini/modules.mk new file mode 100644 index 0000000000..104ad90279 --- /dev/null +++ b/target/linux/gemini/modules.mk @@ -0,0 +1,14 @@ +define KernelPackage/usb-fotg210 + TITLE:=Support for FOTG210 USB host and device controllers + DEPENDS:=@USB_SUPPORT @TARGET_gemini + KCONFIG:=CONFIG_USB_FOTG210 \ + CONFIG_USB_FOTG210_HCD + FILES:=$(if $(CONFIG_USB_FOTG210_HCD),$(LINUX_DIR)/drivers/usb/host/fotg210-hcd.ko@lt6.1) \ + $(if $(CONFIG_USB_FOTG210),$(LINUX_DIR)/drivers/usb/fotg210/fotg210.ko@ge6.1) + AUTOLOAD:=$(call AutoLoad,50, \ + $(if $(CONFIG_USB_FOTG210_HCD),fotg210-hcd@lt6.1) \ + $(if $(CONFIG_USB_FOTG210),fotg210@ge6.1),1) + $(call AddDepends/usb) +endef + +$(eval $(call KernelPackage,usb-fotg210)) From 58acb1dd2c050660ae6dd0985823ef0d2c47bece Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 31 May 2023 23:21:05 +0200 Subject: [PATCH 06/37] gemini: Add kernel v6.1 patches This adds a bunch of patches for the v6.1 Gemini kernel. For v5.15 this was down to a single upstream patch, but for kernel v6.2 I reworked the USB code for FOTG210, so instead of carrying over the half-baked and incomplete patch from v5.15 I just backported all the v6.2 patches, 31 in total, as it creates full device USB mode for e.g. D-Link DNS-313. Signed-off-by: Linus Walleij Signed-off-by: Christian Lamparter --- ...pio-vbus-usb-Add-device-tree-probing.patch | 67 + ...llect-pieces-of-dual-mode-controller.patch | 15993 ++++++++++++++++ ...-usb-fotg210-Compile-into-one-module.patch | 332 + ...usb-fotg210-Select-subdriver-by-mode.patch | 68 + ...b-fotg2-add-Gemini-specific-handling.patch | 135 + ...210-Fix-Kconfig-for-USB-host-modules.patch | 51 + ...FOTG210-should-depend-on-ARCH_GEMINI.patch | 26 + ...dev-pointer-in-probe-and-dev_message.patch | 61 + ...10-udc-Support-optional-external-PHY.patch | 158 + .../0010-fotg210-udc-Handle-PCLK.patch | 90 + ...0-udc-Get-IRQ-using-platform_get_irq.patch | 69 + ...g210-udc-Remove-a-useless-assignment.patch | 39 + ...fix-potential-memory-leak-in-fotg210.patch | 58 + .../0014-usb-fotg210-fix-OTG-only-build.patch | 39 + ...fix-error-return-code-in-fotg210_udc.patch | 28 + ...-usb-fotg210-List-different-variants.patch | 25 + ...g210-Acquire-memory-resource-in-core.patch | 245 + ...-fotg210-Move-clock-handling-to-core.patch | 196 + ...-fotg210-Check-role-register-in-core.patch | 54 + ...dc-Assign-of_node-and-speed-on-start.patch | 34 + ...b-fotg210-udc-Implement-VBUS-session.patch | 96 + ...oduce-and-use-a-fotg210_ack_int-func.patch | 134 + ...10-udc-Improve-device-initialization.patch | 62 + ...use-sysfs_emit-to-instead-of-scnprin.patch | 32 + ...i-Push-down-flash-address-size-cells.patch | 62 + ...ni-wbd111-Use-RedBoot-partion-parser.patch | 54 + ...ni-wbd222-Use-RedBoot-partion-parser.patch | 54 + ...ARM-dts-gemini-Fix-USB-block-version.patch | 31 + ...mini-Enable-DNS313-FOTG210-as-periph.patch | 54 + ...-DIR-685-partition-table-for-OpenWrt.patch | 34 + 30 files changed, 18381 insertions(+) create mode 100644 target/linux/gemini/patches-6.1/0001-usb-phy-phy-gpio-vbus-usb-Add-device-tree-probing.patch create mode 100644 target/linux/gemini/patches-6.1/0002-usb-fotg210-Collect-pieces-of-dual-mode-controller.patch create mode 100644 target/linux/gemini/patches-6.1/0003-usb-fotg210-Compile-into-one-module.patch create mode 100644 target/linux/gemini/patches-6.1/0004-usb-fotg210-Select-subdriver-by-mode.patch create mode 100644 target/linux/gemini/patches-6.1/0005-usb-fotg2-add-Gemini-specific-handling.patch create mode 100644 target/linux/gemini/patches-6.1/0006-usb-fotg210-Fix-Kconfig-for-USB-host-modules.patch create mode 100644 target/linux/gemini/patches-6.1/0007-usb-USB_FOTG210-should-depend-on-ARCH_GEMINI.patch create mode 100644 target/linux/gemini/patches-6.1/0008-fotg210-udc-Use-dev-pointer-in-probe-and-dev_message.patch create mode 100644 target/linux/gemini/patches-6.1/0009-fotg210-udc-Support-optional-external-PHY.patch create mode 100644 target/linux/gemini/patches-6.1/0010-fotg210-udc-Handle-PCLK.patch create mode 100644 target/linux/gemini/patches-6.1/0011-fotg210-udc-Get-IRQ-using-platform_get_irq.patch create mode 100644 target/linux/gemini/patches-6.1/0012-usb-fotg210-udc-Remove-a-useless-assignment.patch create mode 100644 target/linux/gemini/patches-6.1/0013-usb-fotg210-udc-fix-potential-memory-leak-in-fotg210.patch create mode 100644 target/linux/gemini/patches-6.1/0014-usb-fotg210-fix-OTG-only-build.patch create mode 100644 target/linux/gemini/patches-6.1/0015-usb-fotg210-udc-fix-error-return-code-in-fotg210_udc.patch create mode 100644 target/linux/gemini/patches-6.1/0016-usb-fotg210-List-different-variants.patch create mode 100644 target/linux/gemini/patches-6.1/0017-usb-fotg210-Acquire-memory-resource-in-core.patch create mode 100644 target/linux/gemini/patches-6.1/0018-usb-fotg210-Move-clock-handling-to-core.patch create mode 100644 target/linux/gemini/patches-6.1/0019-usb-fotg210-Check-role-register-in-core.patch create mode 100644 target/linux/gemini/patches-6.1/0020-usb-fotg210-udc-Assign-of_node-and-speed-on-start.patch create mode 100644 target/linux/gemini/patches-6.1/0021-usb-fotg210-udc-Implement-VBUS-session.patch create mode 100644 target/linux/gemini/patches-6.1/0022-fotg210-udc-Introduce-and-use-a-fotg210_ack_int-func.patch create mode 100644 target/linux/gemini/patches-6.1/0023-fotg210-udc-Improve-device-initialization.patch create mode 100644 target/linux/gemini/patches-6.1/0024-usb-fotg210-hcd-use-sysfs_emit-to-instead-of-scnprin.patch create mode 100644 target/linux/gemini/patches-6.1/0025-ARM-dts-gemini-Push-down-flash-address-size-cells.patch create mode 100644 target/linux/gemini/patches-6.1/0026-ARM-dts-gemini-wbd111-Use-RedBoot-partion-parser.patch create mode 100644 target/linux/gemini/patches-6.1/0027-ARM-dts-gemini-wbd222-Use-RedBoot-partion-parser.patch create mode 100644 target/linux/gemini/patches-6.1/0028-ARM-dts-gemini-Fix-USB-block-version.patch create mode 100644 target/linux/gemini/patches-6.1/0029-ARM-dts-gemini-Enable-DNS313-FOTG210-as-periph.patch create mode 100644 target/linux/gemini/patches-6.1/300-ARM-dts-Augment-DIR-685-partition-table-for-OpenWrt.patch diff --git a/target/linux/gemini/patches-6.1/0001-usb-phy-phy-gpio-vbus-usb-Add-device-tree-probing.patch b/target/linux/gemini/patches-6.1/0001-usb-phy-phy-gpio-vbus-usb-Add-device-tree-probing.patch new file mode 100644 index 0000000000..943b166d7e --- /dev/null +++ b/target/linux/gemini/patches-6.1/0001-usb-phy-phy-gpio-vbus-usb-Add-device-tree-probing.patch @@ -0,0 +1,67 @@ +From d5a026cc8306ccd3e99e1455c87e38f8e6fa18df Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 7 Nov 2022 00:05:06 +0100 +Subject: [PATCH 01/29] usb: phy: phy-gpio-vbus-usb: Add device tree probing + +Make it possible to probe the GPIO VBUS detection driver +from the device tree compatible for GPIO USB B connectors. + +Since this driver is using the "gpio-usb-b-connector" +compatible, it is important to discern it from the role +switch connector driver (which does not provide a phy), +so we add some Kconfig text and depend on !USB_CONN_GPIO. + +Cc: Rob Herring +Cc: Prashant Malani +Cc: Felipe Balbi +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221106230506.1646101-1-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/phy/Kconfig ++++ b/drivers/usb/phy/Kconfig +@@ -93,12 +93,16 @@ 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. ++ 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" +--- a/drivers/usb/phy/phy-gpio-vbus-usb.c ++++ b/drivers/usb/phy/phy-gpio-vbus-usb.c +@@ -366,12 +366,24 @@ static const struct dev_pm_ops gpio_vbus + + 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", ++ }, ++ {}, ++}; ++ + 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, diff --git a/target/linux/gemini/patches-6.1/0002-usb-fotg210-Collect-pieces-of-dual-mode-controller.patch b/target/linux/gemini/patches-6.1/0002-usb-fotg210-Collect-pieces-of-dual-mode-controller.patch new file mode 100644 index 0000000000..902bf4c68f --- /dev/null +++ b/target/linux/gemini/patches-6.1/0002-usb-fotg210-Collect-pieces-of-dual-mode-controller.patch @@ -0,0 +1,15993 @@ +From 30367636930864f71b2bd462adedcf8484313864 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Sun, 23 Oct 2022 16:47:06 +0200 +Subject: [PATCH 02/29] usb: fotg210: Collect pieces of dual mode controller + +The Faraday FOTG210 is a dual-mode OTG USB controller that can +act as host, peripheral or both. To be able to probe from one +hardware description and to follow the pattern of other dual- +mode controllers such as MUSB or MTU3 we need to collect the +two, currently completely separate drivers in the same +directory. + +After this, users need to select the main symbol USB_FOTG210 +and then each respective subdriver. We pave the road to +compile both drivers into the same kernel and select the +one we want to use at probe() time, and possibly add OTG +support in the end. + +This patch doesn't do much more than create the new symbol +and collect the drivers in one place. We also add a comment +for the section of dual-mode controllers in the Kconfig +file so people can see what these selections are about. + +Also add myself as maintainer as there has been little +response on my patches to these drivers. + +Cc: Fabian Vogt +Cc: Yuan-Hsin Chen +Cc: Felipe Balbi +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221023144708.3596563-1-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/Kconfig ++++ b/drivers/usb/Kconfig +@@ -111,8 +111,12 @@ 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" +--- a/drivers/usb/Makefile ++++ b/drivers/usb/Makefile +@@ -17,6 +17,8 @@ obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns3/ + obj-$(CONFIG_USB_CDNS3) += cdns3/ + obj-$(CONFIG_USB_CDNSP_PCI) += cdns3/ + ++obj-$(CONFIG_USB_FOTG210) += fotg210/ ++ + obj-$(CONFIG_USB_MON) += mon/ + obj-$(CONFIG_USB_MTU3) += mtu3/ + +--- /dev/null ++++ b/drivers/usb/fotg210/Kconfig +@@ -0,0 +1,36 @@ ++# 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 ++ default ARCH_GEMINI ++ 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 ++ tristate "Faraday FOTG210 USB Host Controller support" ++ depends on USB ++ 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 ++ tristate "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 suppports ++ Bulk Transfer so far. ++ ++ Say "y" to link the driver statically, or "m" to build a ++ dynamically linked module called "fotg210-udc". ++ ++endif +--- /dev/null ++++ b/drivers/usb/fotg210/Makefile +@@ -0,0 +1,3 @@ ++# SPDX-License-Identifier: GPL-2.0 ++obj-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o ++obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o +--- a/drivers/usb/host/fotg210-hcd.c ++++ /dev/null +@@ -1,5727 +0,0 @@ +-// 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 +- +-#define DRIVER_AUTHOR "Yuan-Hsin Chen" +-#define DRIVER_DESC "FOTG210 Host Controller (EHCI) Driver" +-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.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 = snprintf(next, size, +- "\n\t%p%c%s len=%d %08x urb %p", +- td, mark, ({ char *tmp; +- switch ((scratch>>8)&0x03) { +- case 0: +- tmp = "out"; +- break; +- case 1: +- tmp = "in"; +- break; +- case 2: +- tmp = "setup"; +- break; +- default: +- tmp = "?"; +- break; +- } tmp; }), +- (scratch >> 16) & 0x7fff, +- scratch, +- td->urb); +- if (size < temp) +- temp = size; +- size -= temp; +- next += temp; +- if (temp == size) +- goto done; +- } +- +- temp = snprintf(next, size, "\n"); +- if (size < temp) +- temp = size; +- +- size -= temp; +- next += temp; +- +-done: +- *sizep = size; +- *nextp = next; +-} +- +-static ssize_t fill_async_buffer(struct debug_buffer *buf) +-{ +- struct usb_hcd *hcd; +- struct 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_array(DBG_SCHED_LIMIT, sizeof(*seen), 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(sizeof(struct debug_buffer), GFP_KERNEL); +- +- if (buf) { +- buf->bus = bus; +- buf->fill_func = fill_func; +- mutex_init(&buf->mutex); +- buf->alloc_size = PAGE_SIZE; +- } +- +- return buf; +-} +- +-static int fill_buffer(struct debug_buffer *buf) +-{ +- int ret = 0; +- +- if (!buf->output_buf) +- buf->output_buf = vmalloc(buf->alloc_size); +- +- if (!buf->output_buf) { +- ret = -ENOMEM; +- goto out; +- } +- +- ret = buf->fill_func(buf); +- +- if (ret >= 0) { +- buf->count = ret; +- ret = 0; +- } +- +-out: +- return ret; +-} +- +-static ssize_t debug_output(struct file *file, char __user *user_buf, +- size_t len, loff_t *offset) +-{ +- struct debug_buffer *buf = file->private_data; +- int ret = 0; +- +- mutex_lock(&buf->mutex); +- if (buf->count == 0) { +- ret = fill_buffer(buf); +- if (ret != 0) { +- mutex_unlock(&buf->mutex); +- goto out; +- } +- } +- mutex_unlock(&buf->mutex); +- +- ret = simple_read_from_buffer(user_buf, len, offset, +- buf->output_buf, buf->count); +- +-out: +- return ret; +- +-} +- +-static int debug_close(struct inode *inode, struct file *file) +-{ +- struct debug_buffer *buf = file->private_data; +- +- if (buf) { +- vfree(buf->output_buf); +- kfree(buf); +- } +- +- return 0; +-} +-static int debug_async_open(struct inode *inode, struct file *file) +-{ +- file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); +- +- return file->private_data ? 0 : -ENOMEM; +-} +- +-static int debug_periodic_open(struct inode *inode, struct file *file) +-{ +- struct debug_buffer *buf; +- +- buf = alloc_buffer(inode->i_private, fill_periodic_buffer); +- if (!buf) +- return -ENOMEM; +- +- buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; +- file->private_data = buf; +- return 0; +-} +- +-static int debug_registers_open(struct inode *inode, struct file *file) +-{ +- file->private_data = alloc_buffer(inode->i_private, +- fill_registers_buffer); +- +- return file->private_data ? 0 : -ENOMEM; +-} +- +-static inline void create_debug_files(struct 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(sizeof(*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(sizeof(*stream), mem_flags); +- if (likely(stream != NULL)) { +- INIT_LIST_HEAD(&stream->td_list); +- INIT_LIST_HEAD(&stream->free_list); +- stream->next_uframe = -1; +- } +- return stream; +-} +- +-static void iso_stream_init(struct 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(struct_size(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: +- iso_sched_free(stream, sched); +- urb->hcpriv = NULL; +- 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: +- 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; +- int n; +- +- fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); +- n = scnprintf(buf, PAGE_SIZE, "%d\n", fotg210->uframe_periodic_max); +- return n; +-} +- +- +-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))); +- if (kstrtouint(buf, 0, &uframe_periodic_max) < 0) +- return -EINVAL; +- +- 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_init(&fotg210->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); +- fotg210->hrtimer.function = fotg210_hrtimer_func; +- 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. +- */ +-MODULE_DESCRIPTION(DRIVER_DESC); +-MODULE_AUTHOR(DRIVER_AUTHOR); +-MODULE_LICENSE("GPL"); +- +-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. +- */ +-static int fotg210_hcd_probe(struct platform_device *pdev) +-{ +- struct device *dev = &pdev->dev; +- struct usb_hcd *hcd; +- struct resource *res; +- 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) { +- dev_err(dev, "failed to create hcd\n"); +- retval = -ENOMEM; +- goto fail_create_hcd; +- } +- +- hcd->has_tt = 1; +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- hcd->regs = devm_ioremap_resource(&pdev->dev, res); +- if (IS_ERR(hcd->regs)) { +- retval = PTR_ERR(hcd->regs); +- goto failed_put_hcd; +- } +- +- hcd->rsrc_start = res->start; +- hcd->rsrc_len = resource_size(res); +- +- fotg210 = hcd_to_fotg210(hcd); +- +- fotg210->caps = hcd->regs; +- +- /* It's OK not to supply this clock */ +- fotg210->pclk = clk_get(dev, "PCLK"); +- if (!IS_ERR(fotg210->pclk)) { +- retval = clk_prepare_enable(fotg210->pclk); +- if (retval) { +- dev_err(dev, "failed to enable PCLK\n"); +- goto failed_put_hcd; +- } +- } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { +- /* +- * Percolate deferrals, for anything else, +- * just live without the clocking. +- */ +- retval = PTR_ERR(fotg210->pclk); +- goto failed_dis_clk; +- } +- +- retval = fotg210_setup(hcd); +- if (retval) +- goto failed_dis_clk; +- +- fotg210_init(fotg210); +- +- retval = usb_add_hcd(hcd, irq, IRQF_SHARED); +- if (retval) { +- dev_err(dev, "failed to add hcd with err %d\n", retval); +- goto failed_dis_clk; +- } +- device_wakeup_enable(hcd->self.controller); +- platform_set_drvdata(pdev, hcd); +- +- return retval; +- +-failed_dis_clk: +- if (!IS_ERR(fotg210->pclk)) { +- clk_disable_unprepare(fotg210->pclk); +- clk_put(fotg210->pclk); +- } +-failed_put_hcd: +- usb_put_hcd(hcd); +-fail_create_hcd: +- dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval); +- return retval; +-} +- +-/* +- * fotg210_hcd_remove - shutdown processing for EHCI HCDs +- * @dev: USB Host Controller being removed +- * +- */ +-static int fotg210_hcd_remove(struct platform_device *pdev) +-{ +- struct usb_hcd *hcd = platform_get_drvdata(pdev); +- struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); +- +- if (!IS_ERR(fotg210->pclk)) { +- clk_disable_unprepare(fotg210->pclk); +- clk_put(fotg210->pclk); +- } +- +- usb_remove_hcd(hcd); +- usb_put_hcd(hcd); +- +- return 0; +-} +- +-#ifdef CONFIG_OF +-static const struct of_device_id fotg210_of_match[] = { +- { .compatible = "faraday,fotg210" }, +- {}, +-}; +-MODULE_DEVICE_TABLE(of, fotg210_of_match); +-#endif +- +-static struct platform_driver fotg210_hcd_driver = { +- .driver = { +- .name = "fotg210-hcd", +- .of_match_table = of_match_ptr(fotg210_of_match), +- }, +- .probe = fotg210_hcd_probe, +- .remove = fotg210_hcd_remove, +-}; +- +-static int __init fotg210_hcd_init(void) +-{ +- int retval = 0; +- +- if (usb_disabled()) +- return -ENODEV; +- +- set_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +- if (test_bit(USB_UHCI_LOADED, &usb_hcds_loaded) || +- test_bit(USB_OHCI_LOADED, &usb_hcds_loaded)) +- pr_warn("Warning! fotg210_hcd should always be loaded before uhci_hcd and ohci_hcd, not after\n"); +- +- pr_debug("%s: block sizes: qh %zd qtd %zd itd %zd\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); +- +- retval = platform_driver_register(&fotg210_hcd_driver); +- if (retval < 0) +- goto clean; +- return retval; +- +-clean: +- debugfs_remove(fotg210_debug_root); +- fotg210_debug_root = NULL; +- +- clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +- return retval; +-} +-module_init(fotg210_hcd_init); +- +-static void __exit fotg210_hcd_cleanup(void) +-{ +- platform_driver_unregister(&fotg210_hcd_driver); +- debugfs_remove(fotg210_debug_root); +- clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +-} +-module_exit(fotg210_hcd_cleanup); +--- /dev/null ++++ b/drivers/usb/fotg210/fotg210-hcd.c +@@ -0,0 +1,5727 @@ ++// 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 ++ ++#define DRIVER_AUTHOR "Yuan-Hsin Chen" ++#define DRIVER_DESC "FOTG210 Host Controller (EHCI) Driver" ++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 = snprintf(next, size, ++ "\n\t%p%c%s len=%d %08x urb %p", ++ td, mark, ({ char *tmp; ++ switch ((scratch>>8)&0x03) { ++ case 0: ++ tmp = "out"; ++ break; ++ case 1: ++ tmp = "in"; ++ break; ++ case 2: ++ tmp = "setup"; ++ break; ++ default: ++ tmp = "?"; ++ break; ++ } tmp; }), ++ (scratch >> 16) & 0x7fff, ++ scratch, ++ td->urb); ++ if (size < temp) ++ temp = size; ++ size -= temp; ++ next += temp; ++ if (temp == size) ++ goto done; ++ } ++ ++ temp = snprintf(next, size, "\n"); ++ if (size < temp) ++ temp = size; ++ ++ size -= temp; ++ next += temp; ++ ++done: ++ *sizep = size; ++ *nextp = next; ++} ++ ++static ssize_t fill_async_buffer(struct debug_buffer *buf) ++{ ++ struct usb_hcd *hcd; ++ struct 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_array(DBG_SCHED_LIMIT, sizeof(*seen), 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(sizeof(struct debug_buffer), GFP_KERNEL); ++ ++ if (buf) { ++ buf->bus = bus; ++ buf->fill_func = fill_func; ++ mutex_init(&buf->mutex); ++ buf->alloc_size = PAGE_SIZE; ++ } ++ ++ return buf; ++} ++ ++static int fill_buffer(struct debug_buffer *buf) ++{ ++ int ret = 0; ++ ++ if (!buf->output_buf) ++ buf->output_buf = vmalloc(buf->alloc_size); ++ ++ if (!buf->output_buf) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ ret = buf->fill_func(buf); ++ ++ if (ret >= 0) { ++ buf->count = ret; ++ ret = 0; ++ } ++ ++out: ++ return ret; ++} ++ ++static ssize_t debug_output(struct file *file, char __user *user_buf, ++ size_t len, loff_t *offset) ++{ ++ struct debug_buffer *buf = file->private_data; ++ int ret = 0; ++ ++ mutex_lock(&buf->mutex); ++ if (buf->count == 0) { ++ ret = fill_buffer(buf); ++ if (ret != 0) { ++ mutex_unlock(&buf->mutex); ++ goto out; ++ } ++ } ++ mutex_unlock(&buf->mutex); ++ ++ ret = simple_read_from_buffer(user_buf, len, offset, ++ buf->output_buf, buf->count); ++ ++out: ++ return ret; ++ ++} ++ ++static int debug_close(struct inode *inode, struct file *file) ++{ ++ struct debug_buffer *buf = file->private_data; ++ ++ if (buf) { ++ vfree(buf->output_buf); ++ kfree(buf); ++ } ++ ++ return 0; ++} ++static int debug_async_open(struct inode *inode, struct file *file) ++{ ++ file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); ++ ++ return file->private_data ? 0 : -ENOMEM; ++} ++ ++static int debug_periodic_open(struct inode *inode, struct file *file) ++{ ++ struct debug_buffer *buf; ++ ++ buf = alloc_buffer(inode->i_private, fill_periodic_buffer); ++ if (!buf) ++ return -ENOMEM; ++ ++ buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; ++ file->private_data = buf; ++ return 0; ++} ++ ++static int debug_registers_open(struct inode *inode, struct file *file) ++{ ++ file->private_data = alloc_buffer(inode->i_private, ++ fill_registers_buffer); ++ ++ return file->private_data ? 0 : -ENOMEM; ++} ++ ++static inline void create_debug_files(struct 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(sizeof(*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(sizeof(*stream), mem_flags); ++ if (likely(stream != NULL)) { ++ INIT_LIST_HEAD(&stream->td_list); ++ INIT_LIST_HEAD(&stream->free_list); ++ stream->next_uframe = -1; ++ } ++ return stream; ++} ++ ++static void iso_stream_init(struct 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(struct_size(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: ++ iso_sched_free(stream, sched); ++ urb->hcpriv = NULL; ++ 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: ++ 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; ++ int n; ++ ++ fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); ++ n = scnprintf(buf, PAGE_SIZE, "%d\n", fotg210->uframe_periodic_max); ++ return n; ++} ++ ++ ++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))); ++ if (kstrtouint(buf, 0, &uframe_periodic_max) < 0) ++ return -EINVAL; ++ ++ 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_init(&fotg210->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); ++ fotg210->hrtimer.function = fotg210_hrtimer_func; ++ 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. ++ */ ++MODULE_DESCRIPTION(DRIVER_DESC); ++MODULE_AUTHOR(DRIVER_AUTHOR); ++MODULE_LICENSE("GPL"); ++ ++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. ++ */ ++static int fotg210_hcd_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct usb_hcd *hcd; ++ struct resource *res; ++ 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) { ++ dev_err(dev, "failed to create hcd\n"); ++ retval = -ENOMEM; ++ goto fail_create_hcd; ++ } ++ ++ hcd->has_tt = 1; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ hcd->regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(hcd->regs)) { ++ retval = PTR_ERR(hcd->regs); ++ goto failed_put_hcd; ++ } ++ ++ hcd->rsrc_start = res->start; ++ hcd->rsrc_len = resource_size(res); ++ ++ fotg210 = hcd_to_fotg210(hcd); ++ ++ fotg210->caps = hcd->regs; ++ ++ /* It's OK not to supply this clock */ ++ fotg210->pclk = clk_get(dev, "PCLK"); ++ if (!IS_ERR(fotg210->pclk)) { ++ retval = clk_prepare_enable(fotg210->pclk); ++ if (retval) { ++ dev_err(dev, "failed to enable PCLK\n"); ++ goto failed_put_hcd; ++ } ++ } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { ++ /* ++ * Percolate deferrals, for anything else, ++ * just live without the clocking. ++ */ ++ retval = PTR_ERR(fotg210->pclk); ++ goto failed_dis_clk; ++ } ++ ++ retval = fotg210_setup(hcd); ++ if (retval) ++ goto failed_dis_clk; ++ ++ fotg210_init(fotg210); ++ ++ retval = usb_add_hcd(hcd, irq, IRQF_SHARED); ++ if (retval) { ++ dev_err(dev, "failed to add hcd with err %d\n", retval); ++ goto failed_dis_clk; ++ } ++ device_wakeup_enable(hcd->self.controller); ++ platform_set_drvdata(pdev, hcd); ++ ++ return retval; ++ ++failed_dis_clk: ++ if (!IS_ERR(fotg210->pclk)) { ++ clk_disable_unprepare(fotg210->pclk); ++ clk_put(fotg210->pclk); ++ } ++failed_put_hcd: ++ usb_put_hcd(hcd); ++fail_create_hcd: ++ dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval); ++ return retval; ++} ++ ++/* ++ * fotg210_hcd_remove - shutdown processing for EHCI HCDs ++ * @dev: USB Host Controller being removed ++ * ++ */ ++static int fotg210_hcd_remove(struct platform_device *pdev) ++{ ++ struct usb_hcd *hcd = platform_get_drvdata(pdev); ++ struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); ++ ++ if (!IS_ERR(fotg210->pclk)) { ++ clk_disable_unprepare(fotg210->pclk); ++ clk_put(fotg210->pclk); ++ } ++ ++ usb_remove_hcd(hcd); ++ usb_put_hcd(hcd); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_OF ++static const struct of_device_id fotg210_of_match[] = { ++ { .compatible = "faraday,fotg210" }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, fotg210_of_match); ++#endif ++ ++static struct platform_driver fotg210_hcd_driver = { ++ .driver = { ++ .name = "fotg210-hcd", ++ .of_match_table = of_match_ptr(fotg210_of_match), ++ }, ++ .probe = fotg210_hcd_probe, ++ .remove = fotg210_hcd_remove, ++}; ++ ++static int __init fotg210_hcd_init(void) ++{ ++ int retval = 0; ++ ++ if (usb_disabled()) ++ return -ENODEV; ++ ++ set_bit(USB_EHCI_LOADED, &usb_hcds_loaded); ++ if (test_bit(USB_UHCI_LOADED, &usb_hcds_loaded) || ++ test_bit(USB_OHCI_LOADED, &usb_hcds_loaded)) ++ pr_warn("Warning! fotg210_hcd should always be loaded before uhci_hcd and ohci_hcd, not after\n"); ++ ++ pr_debug("%s: block sizes: qh %zd qtd %zd itd %zd\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); ++ ++ retval = platform_driver_register(&fotg210_hcd_driver); ++ if (retval < 0) ++ goto clean; ++ return retval; ++ ++clean: ++ debugfs_remove(fotg210_debug_root); ++ fotg210_debug_root = NULL; ++ ++ clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); ++ return retval; ++} ++module_init(fotg210_hcd_init); ++ ++static void __exit fotg210_hcd_cleanup(void) ++{ ++ platform_driver_unregister(&fotg210_hcd_driver); ++ debugfs_remove(fotg210_debug_root); ++ clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); ++} ++module_exit(fotg210_hcd_cleanup); +--- a/drivers/usb/gadget/udc/fotg210-udc.c ++++ /dev/null +@@ -1,1239 +0,0 @@ +-// 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 "fotg210.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_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(sizeof(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)); +- +- value &= ~DISGR2_DMA_CMPLT; +- iowrite32(value, ep->fotg210->reg + FOTG210_DISGR2); +- 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_clear_comabt_int(struct fotg210_udc *fotg210) +-{ +- u32 value = ioread32(fotg210->reg + FOTG210_DISGR0); +- +- value &= ~DISGR0_CX_COMABT_INT; +- iowrite32(value, fotg210->reg + FOTG210_DISGR0); +-} +- +-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); +- u32 value; +- +- int_grp2 &= ~int_msk2; +- +- if (int_grp2 & DISGR2_USBRST_INT) { +- usb_gadget_udc_reset(&fotg210->gadget, +- fotg210->driver); +- value = ioread32(reg); +- value &= ~DISGR2_USBRST_INT; +- iowrite32(value, reg); +- pr_info("fotg210 udc reset\n"); +- } +- if (int_grp2 & DISGR2_SUSP_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_SUSP_INT; +- iowrite32(value, reg); +- pr_info("fotg210 udc suspend\n"); +- } +- if (int_grp2 & DISGR2_RESM_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_RESM_INT; +- iowrite32(value, reg); +- pr_info("fotg210 udc resume\n"); +- } +- if (int_grp2 & DISGR2_ISO_SEQ_ERR_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_ISO_SEQ_ERR_INT; +- iowrite32(value, reg); +- pr_info("fotg210 iso sequence error\n"); +- } +- if (int_grp2 & DISGR2_ISO_SEQ_ABORT_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_ISO_SEQ_ABORT_INT; +- iowrite32(value, reg); +- pr_info("fotg210 iso sequence abort\n"); +- } +- if (int_grp2 & DISGR2_TX0BYTE_INT) { +- fotg210_clear_tx0byte(fotg210); +- value = ioread32(reg); +- value &= ~DISGR2_TX0BYTE_INT; +- iowrite32(value, reg); +- pr_info("fotg210 transferred 0 byte\n"); +- } +- if (int_grp2 & DISGR2_RX0BYTE_INT) { +- fotg210_clear_rx0byte(fotg210); +- value = ioread32(reg); +- value &= ~DISGR2_RX0BYTE_INT; +- iowrite32(value, reg); +- pr_info("fotg210 received 0 byte\n"); +- } +- if (int_grp2 & DISGR2_DMA_ERROR) { +- value = ioread32(reg); +- value &= ~DISGR2_DMA_ERROR; +- iowrite32(value, reg); +- } +- } +- +- 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_clear_comabt_int(fotg210); +- 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; +- +- /* hook up the driver */ +- fotg210->driver = driver; +- +- /* 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); +- +- /* 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; +- +- spin_lock_irqsave(&fotg210->lock, flags); +- +- fotg210_init(fotg210); +- fotg210->driver = NULL; +- +- spin_unlock_irqrestore(&fotg210->lock, flags); +- +- return 0; +-} +- +-static const struct usb_gadget_ops fotg210_gadget_ops = { +- .udc_start = fotg210_udc_start, +- .udc_stop = fotg210_udc_stop, +-}; +- +-static int fotg210_udc_remove(struct platform_device *pdev) +-{ +- struct fotg210_udc *fotg210 = platform_get_drvdata(pdev); +- int i; +- +- usb_del_gadget_udc(&fotg210->gadget); +- 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; +-} +- +-static int fotg210_udc_probe(struct platform_device *pdev) +-{ +- struct resource *res, *ires; +- struct fotg210_udc *fotg210 = NULL; +- struct fotg210_ep *_ep[FOTG210_MAX_NUM_EP]; +- int ret = 0; +- int i; +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!res) { +- pr_err("platform_get_resource error.\n"); +- return -ENODEV; +- } +- +- ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +- if (!ires) { +- pr_err("platform_get_resource IORESOURCE_IRQ error.\n"); +- return -ENODEV; +- } +- +- ret = -ENOMEM; +- +- /* initialize udc */ +- fotg210 = kzalloc(sizeof(struct fotg210_udc), GFP_KERNEL); +- if (fotg210 == NULL) +- goto err; +- +- for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { +- _ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); +- if (_ep[i] == NULL) +- goto err_alloc; +- fotg210->ep[i] = _ep[i]; +- } +- +- fotg210->reg = ioremap(res->start, resource_size(res)); +- if (fotg210->reg == NULL) { +- pr_err("ioremap error.\n"); +- goto err_alloc; +- } +- +- 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 = &pdev->dev; +- fotg210->gadget.dev.dma_mask = pdev->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(ires->start, fotg210_irq, IRQF_SHARED, +- udc_name, fotg210); +- if (ret < 0) { +- pr_err("request_irq error (%d)\n", ret); +- goto err_req; +- } +- +- ret = usb_add_gadget_udc(&pdev->dev, &fotg210->gadget); +- if (ret) +- goto err_add_udc; +- +- dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); +- +- return 0; +- +-err_add_udc: +- free_irq(ires->start, 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]); +- kfree(fotg210); +- +-err: +- return ret; +-} +- +-static struct platform_driver fotg210_driver = { +- .driver = { +- .name = udc_name, +- }, +- .probe = fotg210_udc_probe, +- .remove = fotg210_udc_remove, +-}; +- +-module_platform_driver(fotg210_driver); +- +-MODULE_AUTHOR("Yuan-Hsin Chen, Feng-Hsin Chiang "); +-MODULE_LICENSE("GPL"); +-MODULE_DESCRIPTION(DRIVER_DESC); +--- /dev/null ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -0,0 +1,1239 @@ ++// 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 "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_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(sizeof(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)); ++ ++ value &= ~DISGR2_DMA_CMPLT; ++ iowrite32(value, ep->fotg210->reg + FOTG210_DISGR2); ++ 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_clear_comabt_int(struct fotg210_udc *fotg210) ++{ ++ u32 value = ioread32(fotg210->reg + FOTG210_DISGR0); ++ ++ value &= ~DISGR0_CX_COMABT_INT; ++ iowrite32(value, fotg210->reg + FOTG210_DISGR0); ++} ++ ++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); ++ u32 value; ++ ++ int_grp2 &= ~int_msk2; ++ ++ if (int_grp2 & DISGR2_USBRST_INT) { ++ usb_gadget_udc_reset(&fotg210->gadget, ++ fotg210->driver); ++ value = ioread32(reg); ++ value &= ~DISGR2_USBRST_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 udc reset\n"); ++ } ++ if (int_grp2 & DISGR2_SUSP_INT) { ++ value = ioread32(reg); ++ value &= ~DISGR2_SUSP_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 udc suspend\n"); ++ } ++ if (int_grp2 & DISGR2_RESM_INT) { ++ value = ioread32(reg); ++ value &= ~DISGR2_RESM_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 udc resume\n"); ++ } ++ if (int_grp2 & DISGR2_ISO_SEQ_ERR_INT) { ++ value = ioread32(reg); ++ value &= ~DISGR2_ISO_SEQ_ERR_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 iso sequence error\n"); ++ } ++ if (int_grp2 & DISGR2_ISO_SEQ_ABORT_INT) { ++ value = ioread32(reg); ++ value &= ~DISGR2_ISO_SEQ_ABORT_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 iso sequence abort\n"); ++ } ++ if (int_grp2 & DISGR2_TX0BYTE_INT) { ++ fotg210_clear_tx0byte(fotg210); ++ value = ioread32(reg); ++ value &= ~DISGR2_TX0BYTE_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 transferred 0 byte\n"); ++ } ++ if (int_grp2 & DISGR2_RX0BYTE_INT) { ++ fotg210_clear_rx0byte(fotg210); ++ value = ioread32(reg); ++ value &= ~DISGR2_RX0BYTE_INT; ++ iowrite32(value, reg); ++ pr_info("fotg210 received 0 byte\n"); ++ } ++ if (int_grp2 & DISGR2_DMA_ERROR) { ++ value = ioread32(reg); ++ value &= ~DISGR2_DMA_ERROR; ++ iowrite32(value, reg); ++ } ++ } ++ ++ 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_clear_comabt_int(fotg210); ++ 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; ++ ++ /* hook up the driver */ ++ fotg210->driver = driver; ++ ++ /* 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); ++ ++ /* 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; ++ ++ spin_lock_irqsave(&fotg210->lock, flags); ++ ++ fotg210_init(fotg210); ++ fotg210->driver = NULL; ++ ++ spin_unlock_irqrestore(&fotg210->lock, flags); ++ ++ return 0; ++} ++ ++static const struct usb_gadget_ops fotg210_gadget_ops = { ++ .udc_start = fotg210_udc_start, ++ .udc_stop = fotg210_udc_stop, ++}; ++ ++static int fotg210_udc_remove(struct platform_device *pdev) ++{ ++ struct fotg210_udc *fotg210 = platform_get_drvdata(pdev); ++ int i; ++ ++ usb_del_gadget_udc(&fotg210->gadget); ++ 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; ++} ++ ++static int fotg210_udc_probe(struct platform_device *pdev) ++{ ++ struct resource *res, *ires; ++ struct fotg210_udc *fotg210 = NULL; ++ struct fotg210_ep *_ep[FOTG210_MAX_NUM_EP]; ++ int ret = 0; ++ int i; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ pr_err("platform_get_resource error.\n"); ++ return -ENODEV; ++ } ++ ++ ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); ++ if (!ires) { ++ pr_err("platform_get_resource IORESOURCE_IRQ error.\n"); ++ return -ENODEV; ++ } ++ ++ ret = -ENOMEM; ++ ++ /* initialize udc */ ++ fotg210 = kzalloc(sizeof(struct fotg210_udc), GFP_KERNEL); ++ if (fotg210 == NULL) ++ goto err; ++ ++ for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { ++ _ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); ++ if (_ep[i] == NULL) ++ goto err_alloc; ++ fotg210->ep[i] = _ep[i]; ++ } ++ ++ fotg210->reg = ioremap(res->start, resource_size(res)); ++ if (fotg210->reg == NULL) { ++ pr_err("ioremap error.\n"); ++ goto err_alloc; ++ } ++ ++ 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 = &pdev->dev; ++ fotg210->gadget.dev.dma_mask = pdev->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(ires->start, fotg210_irq, IRQF_SHARED, ++ udc_name, fotg210); ++ if (ret < 0) { ++ pr_err("request_irq error (%d)\n", ret); ++ goto err_req; ++ } ++ ++ ret = usb_add_gadget_udc(&pdev->dev, &fotg210->gadget); ++ if (ret) ++ goto err_add_udc; ++ ++ dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); ++ ++ return 0; ++ ++err_add_udc: ++ free_irq(ires->start, 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]); ++ kfree(fotg210); ++ ++err: ++ return ret; ++} ++ ++static struct platform_driver fotg210_driver = { ++ .driver = { ++ .name = udc_name, ++ }, ++ .probe = fotg210_udc_probe, ++ .remove = fotg210_udc_remove, ++}; ++ ++module_platform_driver(fotg210_driver); ++ ++MODULE_AUTHOR("Yuan-Hsin Chen, Feng-Hsin Chiang "); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION(DRIVER_DESC); +--- a/drivers/usb/gadget/udc/Kconfig ++++ b/drivers/usb/gadget/udc/Kconfig +@@ -108,17 +108,6 @@ config USB_FUSB300 + help + Faraday usb device controller FUSB300 driver + +-config USB_FOTG210_UDC +- depends on HAS_DMA +- tristate "Faraday FOTG210 USB Peripheral Controller" +- help +- Faraday USB2.0 OTG controller which can be configured as +- high speed or full speed USB device. This driver supppors +- Bulk Transfer so far. +- +- Say "y" to link the driver statically, or "m" to build a +- dynamically linked module called "fotg210_udc". +- + config USB_GR_UDC + tristate "Aeroflex Gaisler GRUSBDC USB Peripheral Controller Driver" + depends on HAS_DMA +--- a/drivers/usb/gadget/udc/Makefile ++++ b/drivers/usb/gadget/udc/Makefile +@@ -34,7 +34,6 @@ obj-$(CONFIG_USB_EG20T) += pch_udc.o + obj-$(CONFIG_USB_MV_UDC) += mv_udc.o + mv_udc-y := mv_udc_core.o + obj-$(CONFIG_USB_FUSB300) += fusb300_udc.o +-obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o + obj-$(CONFIG_USB_MV_U3D) += mv_u3d_core.o + obj-$(CONFIG_USB_GR_UDC) += gr_udc.o + obj-$(CONFIG_USB_GADGET_XILINX) += udc-xilinx.o +--- a/drivers/usb/host/Kconfig ++++ b/drivers/usb/host/Kconfig +@@ -389,17 +389,6 @@ config USB_ISP1362_HCD + To compile this driver as a module, choose M here: the + module will be called isp1362-hcd. + +-config USB_FOTG210_HCD +- tristate "FOTG210 HCD support" +- depends on USB && HAS_DMA && HAS_IOMEM +- 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_MAX3421_HCD + tristate "MAX3421 HCD (USB-over-SPI) support" + depends on USB && SPI +--- a/drivers/usb/host/Makefile ++++ b/drivers/usb/host/Makefile +@@ -84,6 +84,5 @@ 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_FOTG210_HCD) += fotg210-hcd.o + obj-$(CONFIG_USB_MAX3421_HCD) += max3421-hcd.o + obj-$(CONFIG_USB_XEN_HCD) += xen-hcd.o +--- /dev/null ++++ b/drivers/usb/fotg210/fotg210-hcd.h +@@ -0,0 +1,688 @@ ++/* 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 ++ ++ /* 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 */ +--- /dev/null ++++ b/drivers/usb/fotg210/fotg210-udc.h +@@ -0,0 +1,249 @@ ++// 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_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 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) +--- a/drivers/usb/gadget/udc/fotg210.h ++++ /dev/null +@@ -1,249 +0,0 @@ +-// 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_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 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) +--- a/drivers/usb/host/fotg210.h ++++ /dev/null +@@ -1,688 +0,0 @@ +-/* 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 +- +- /* 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/target/linux/gemini/patches-6.1/0003-usb-fotg210-Compile-into-one-module.patch b/target/linux/gemini/patches-6.1/0003-usb-fotg210-Compile-into-one-module.patch new file mode 100644 index 0000000000..5c7b4ff9c7 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0003-usb-fotg210-Compile-into-one-module.patch @@ -0,0 +1,332 @@ +From 0dbc77a99267a5efef0603a4b49ac02ece6a3f23 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Sun, 23 Oct 2022 16:47:07 +0200 +Subject: [PATCH 03/29] usb: fotg210: Compile into one module + +It is since ages perfectly possible to compile both of these +modules into the same kernel, which makes no sense since it +is one piece of hardware. + +Compile one module named "fotg210.ko" for both HCD and UDC +drivers by collecting the init calls into a fotg210-core.c +file and start to centralize things handling one and the same +piece of hardware. + +Stub out the initcalls if one or the other part of the driver +was not selected. + +Tested by compiling one or the other or both of the drivers +into the kernel and as modules. + +Cc: Fabian Vogt +Cc: Yuan-Hsin Chen +Cc: Felipe Balbi +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221023144708.3596563-2-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/Kconfig ++++ b/drivers/usb/fotg210/Kconfig +@@ -12,7 +12,7 @@ config USB_FOTG210 + if USB_FOTG210 + + config USB_FOTG210_HCD +- tristate "Faraday FOTG210 USB Host Controller support" ++ bool "Faraday FOTG210 USB Host Controller support" + depends on USB + help + Faraday FOTG210 is an OTG controller which can be configured as +@@ -24,7 +24,7 @@ config USB_FOTG210_HCD + + config USB_FOTG210_UDC + depends on USB_GADGET +- tristate "Faraday FOTG210 USB Peripheral Controller support" ++ 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 suppports +--- a/drivers/usb/fotg210/Makefile ++++ b/drivers/usb/fotg210/Makefile +@@ -1,3 +1,10 @@ + # SPDX-License-Identifier: GPL-2.0 +-obj-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o +-obj-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o ++ ++# This setup links the different object files into one single ++# module so we don't have to EXPORT() a lot of internal symbols ++# or create unnecessary submodules. ++fotg210-objs-y += fotg210-core.o ++fotg210-objs-$(CONFIG_USB_FOTG210_HCD) += fotg210-hcd.o ++fotg210-objs-$(CONFIG_USB_FOTG210_UDC) += fotg210-udc.o ++fotg210-objs := $(fotg210-objs-y) ++obj-$(CONFIG_USB_FOTG210) += fotg210.o +--- /dev/null ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -0,0 +1,79 @@ ++// 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 "fotg210.h" ++ ++static int fotg210_probe(struct platform_device *pdev) ++{ ++ int ret; ++ ++ if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) { ++ ret = fotg210_hcd_probe(pdev); ++ if (ret) ++ return ret; ++ } ++ if (IS_ENABLED(CONFIG_USB_FOTG210_UDC)) ++ ret = fotg210_udc_probe(pdev); ++ ++ return ret; ++} ++ ++static int fotg210_remove(struct platform_device *pdev) ++{ ++ if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) ++ fotg210_hcd_remove(pdev); ++ if (IS_ENABLED(CONFIG_USB_FOTG210_UDC)) ++ fotg210_udc_remove(pdev); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_OF ++static const struct of_device_id fotg210_of_match[] = { ++ { .compatible = "faraday,fotg210" }, ++ {}, ++}; ++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 (usb_disabled()) ++ return -ENODEV; ++ ++ if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) ++ 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"); +--- a/drivers/usb/fotg210/fotg210-hcd.c ++++ b/drivers/usb/fotg210/fotg210-hcd.c +@@ -39,8 +39,8 @@ + #include + #include + +-#define DRIVER_AUTHOR "Yuan-Hsin Chen" +-#define DRIVER_DESC "FOTG210 Host Controller (EHCI) Driver" ++#include "fotg210.h" ++ + static const char hcd_name[] = "fotg210_hcd"; + + #undef FOTG210_URB_TRACE +@@ -5490,9 +5490,6 @@ static int fotg210_get_frame(struct usb_ + * functions and in order to facilitate role switching we cannot + * give the fotg210 driver exclusive access to those. + */ +-MODULE_DESCRIPTION(DRIVER_DESC); +-MODULE_AUTHOR(DRIVER_AUTHOR); +-MODULE_LICENSE("GPL"); + + static const struct hc_driver fotg210_fotg210_hc_driver = { + .description = hcd_name, +@@ -5560,7 +5557,7 @@ static void fotg210_init(struct fotg210_ + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + */ +-static int fotg210_hcd_probe(struct platform_device *pdev) ++int fotg210_hcd_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; + struct usb_hcd *hcd; +@@ -5652,7 +5649,7 @@ fail_create_hcd: + * @dev: USB Host Controller being removed + * + */ +-static int fotg210_hcd_remove(struct platform_device *pdev) ++int fotg210_hcd_remove(struct platform_device *pdev) + { + struct usb_hcd *hcd = platform_get_drvdata(pdev); + struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); +@@ -5668,27 +5665,8 @@ static int fotg210_hcd_remove(struct pla + return 0; + } + +-#ifdef CONFIG_OF +-static const struct of_device_id fotg210_of_match[] = { +- { .compatible = "faraday,fotg210" }, +- {}, +-}; +-MODULE_DEVICE_TABLE(of, fotg210_of_match); +-#endif +- +-static struct platform_driver fotg210_hcd_driver = { +- .driver = { +- .name = "fotg210-hcd", +- .of_match_table = of_match_ptr(fotg210_of_match), +- }, +- .probe = fotg210_hcd_probe, +- .remove = fotg210_hcd_remove, +-}; +- +-static int __init fotg210_hcd_init(void) ++int __init fotg210_hcd_init(void) + { +- int retval = 0; +- + if (usb_disabled()) + return -ENODEV; + +@@ -5704,24 +5682,11 @@ static int __init fotg210_hcd_init(void) + + fotg210_debug_root = debugfs_create_dir("fotg210", usb_debug_root); + +- retval = platform_driver_register(&fotg210_hcd_driver); +- if (retval < 0) +- goto clean; +- return retval; +- +-clean: +- debugfs_remove(fotg210_debug_root); +- fotg210_debug_root = NULL; +- +- clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); +- return retval; ++ return 0; + } +-module_init(fotg210_hcd_init); + +-static void __exit fotg210_hcd_cleanup(void) ++void __exit fotg210_hcd_cleanup(void) + { +- platform_driver_unregister(&fotg210_hcd_driver); + debugfs_remove(fotg210_debug_root); + clear_bit(USB_EHCI_LOADED, &usb_hcds_loaded); + } +-module_exit(fotg210_hcd_cleanup); +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -16,6 +16,7 @@ + #include + #include + ++#include "fotg210.h" + #include "fotg210-udc.h" + + #define DRIVER_DESC "FOTG210 USB Device Controller Driver" +@@ -1081,7 +1082,7 @@ static const struct usb_gadget_ops fotg2 + .udc_stop = fotg210_udc_stop, + }; + +-static int fotg210_udc_remove(struct platform_device *pdev) ++int fotg210_udc_remove(struct platform_device *pdev) + { + struct fotg210_udc *fotg210 = platform_get_drvdata(pdev); + int i; +@@ -1098,7 +1099,7 @@ static int fotg210_udc_remove(struct pla + return 0; + } + +-static int fotg210_udc_probe(struct platform_device *pdev) ++int fotg210_udc_probe(struct platform_device *pdev) + { + struct resource *res, *ires; + struct fotg210_udc *fotg210 = NULL; +@@ -1223,17 +1224,3 @@ err_alloc: + err: + return ret; + } +- +-static struct platform_driver fotg210_driver = { +- .driver = { +- .name = udc_name, +- }, +- .probe = fotg210_udc_probe, +- .remove = fotg210_udc_remove, +-}; +- +-module_platform_driver(fotg210_driver); +- +-MODULE_AUTHOR("Yuan-Hsin Chen, Feng-Hsin Chiang "); +-MODULE_LICENSE("GPL"); +-MODULE_DESCRIPTION(DRIVER_DESC); +--- /dev/null ++++ b/drivers/usb/fotg210/fotg210.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++#ifndef __FOTG210_H ++#define __FOTG210_H ++ ++#ifdef CONFIG_USB_FOTG210_HCD ++int fotg210_hcd_probe(struct platform_device *pdev); ++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) ++{ ++ 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); ++int fotg210_udc_remove(struct platform_device *pdev); ++#else ++static inline int fotg210_udc_probe(struct platform_device *pdev) ++{ ++ return 0; ++} ++static inline int fotg210_udc_remove(struct platform_device *pdev) ++{ ++ return 0; ++} ++#endif ++ ++#endif /* __FOTG210_H */ diff --git a/target/linux/gemini/patches-6.1/0004-usb-fotg210-Select-subdriver-by-mode.patch b/target/linux/gemini/patches-6.1/0004-usb-fotg210-Select-subdriver-by-mode.patch new file mode 100644 index 0000000000..6a19a0aa4d --- /dev/null +++ b/target/linux/gemini/patches-6.1/0004-usb-fotg210-Select-subdriver-by-mode.patch @@ -0,0 +1,68 @@ +From 7c0b661926097e935f2711857596fc2277b2304a Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Sun, 23 Oct 2022 16:47:08 +0200 +Subject: [PATCH 04/29] usb: fotg210: Select subdriver by mode + +Check which mode the hardware is in, and selecte the peripheral +driver if the hardware is in explicit peripheral mode, otherwise +select host mode. + +This should solve the immediate problem that both subdrivers +can get probed. + +Cc: Fabian Vogt +Cc: Yuan-Hsin Chen +Cc: Felipe Balbi +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221023144708.3596563-3-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -10,30 +10,37 @@ + #include + #include + #include ++#include + + #include "fotg210.h" + + static int fotg210_probe(struct platform_device *pdev) + { ++ struct device *dev = &pdev->dev; ++ enum usb_dr_mode mode; + int ret; + +- if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) { +- ret = fotg210_hcd_probe(pdev); +- if (ret) +- return ret; +- } +- if (IS_ENABLED(CONFIG_USB_FOTG210_UDC)) ++ mode = usb_get_dr_mode(dev); ++ ++ if (mode == USB_DR_MODE_PERIPHERAL) + ret = fotg210_udc_probe(pdev); ++ else ++ ret = fotg210_hcd_probe(pdev); + + return ret; + } + + static int fotg210_remove(struct platform_device *pdev) + { +- if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) +- fotg210_hcd_remove(pdev); +- if (IS_ENABLED(CONFIG_USB_FOTG210_UDC)) ++ 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); + + return 0; + } diff --git a/target/linux/gemini/patches-6.1/0005-usb-fotg2-add-Gemini-specific-handling.patch b/target/linux/gemini/patches-6.1/0005-usb-fotg2-add-Gemini-specific-handling.patch new file mode 100644 index 0000000000..daf8d85611 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0005-usb-fotg2-add-Gemini-specific-handling.patch @@ -0,0 +1,135 @@ +From f7f6c8aca91093e2f886ec97910b1a7d9a69bf9b Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 9 Nov 2022 21:05:54 +0100 +Subject: [PATCH 05/29] usb: fotg2: add Gemini-specific handling + +The Cortina Systems Gemini has bolted on a PHY inside the +silicon that can be handled by six bits in a MISC register in +the system controller. + +If we are running on Gemini, look up a syscon regmap through +a phandle and enable VBUS and optionally the Mini-B connector. + +If the device is flagged as "wakeup-source" using the standard +DT bindings, we also enable this in the global controller for +respective port. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221109200554.1957185-1-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/Kconfig ++++ b/drivers/usb/fotg210/Kconfig +@@ -5,6 +5,7 @@ config USB_FOTG210 + depends on USB || USB_GADGET + depends on HAS_DMA && HAS_IOMEM + 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. +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -5,15 +5,86 @@ + * whether to proceed with probing the host or the peripheral + * driver. + */ ++#include + #include ++#include + #include + #include + #include ++#include + #include + #include + + #include "fotg210.h" + ++/* ++ * 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 device *dev, struct resource *res, ++ enum usb_dr_mode mode) ++{ ++ 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)) { ++ dev_err(dev, "no syscon\n"); ++ return PTR_ERR(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) { ++ 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 { ++ 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; ++} ++ + static int fotg210_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -22,6 +93,15 @@ static int fotg210_probe(struct platform + + mode = usb_get_dr_mode(dev); + ++ if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) { ++ struct resource *res; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ret = fotg210_gemini_init(dev, res, mode); ++ if (ret) ++ return ret; ++ } ++ + if (mode == USB_DR_MODE_PERIPHERAL) + ret = fotg210_udc_probe(pdev); + else diff --git a/target/linux/gemini/patches-6.1/0006-usb-fotg210-Fix-Kconfig-for-USB-host-modules.patch b/target/linux/gemini/patches-6.1/0006-usb-fotg210-Fix-Kconfig-for-USB-host-modules.patch new file mode 100644 index 0000000000..bd3a42415a --- /dev/null +++ b/target/linux/gemini/patches-6.1/0006-usb-fotg210-Fix-Kconfig-for-USB-host-modules.patch @@ -0,0 +1,51 @@ +From 6e002d41889bc52213a26ff91338d340505e0336 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Fri, 11 Nov 2022 15:48:21 +0100 +Subject: [PATCH 06/29] usb: fotg210: Fix Kconfig for USB host modules + +The kernel robot reports a link failure when activating the +FOTG210 host subdriver with =y on a system where the USB host +core is a module (CONFIG_USB=m). + +This is a bit of special case, so mimic the Kconfig incantations +from DWC3: let the subdrivers for host or peripheral depend +on the host or gadget support being =y or the same as the +FOTG210 core itself. + +This should ensure that either: + +- The host (CONFIG_USB) or gadget (CONFIG_GADGET) is compiled + in and then the FOTG210 can be either module or compiled + in. + +- The host or gadget is modular, and then the FOTG210 module + must be a module too, or we cannot resolve the symbols + at link time. + +Reported-by: kernel test robot +Link: https://lore.kernel.org/linux-usb/202211112132.0BUPGKCd-lkp@intel.com/ +Cc: Arnd Bergmann +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221111144821.113665-1-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/Kconfig ++++ b/drivers/usb/fotg210/Kconfig +@@ -14,7 +14,7 @@ if USB_FOTG210 + + config USB_FOTG210_HCD + bool "Faraday FOTG210 USB Host Controller support" +- depends on USB ++ 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 +@@ -24,7 +24,7 @@ config USB_FOTG210_HCD + module will be called fotg210-hcd. + + config USB_FOTG210_UDC +- depends on USB_GADGET ++ 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 diff --git a/target/linux/gemini/patches-6.1/0007-usb-USB_FOTG210-should-depend-on-ARCH_GEMINI.patch b/target/linux/gemini/patches-6.1/0007-usb-USB_FOTG210-should-depend-on-ARCH_GEMINI.patch new file mode 100644 index 0000000000..6afef0d820 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0007-usb-USB_FOTG210-should-depend-on-ARCH_GEMINI.patch @@ -0,0 +1,26 @@ +From 466b10510add46afd21ca19505b29d35ad853370 Mon Sep 17 00:00:00 2001 +From: Geert Uytterhoeven +Date: Mon, 21 Nov 2022 16:22:19 +0100 +Subject: [PATCH 07/29] usb: USB_FOTG210 should depend on ARCH_GEMINI + +The Faraday Technology FOTG210 USB2 Dual Role Controller is only present +on Cortina Systems Gemini SoCs. Hence add a dependency on ARCH_GEMINI, +to prevent asking the user about its drivers when configuring a kernel +without Cortina Systems Gemini SoC support. + +Fixes: 1dd33a9f1b95ab59 ("usb: fotg210: Collect pieces of dual mode controller") +Signed-off-by: Geert Uytterhoeven +Reviewed-by: Linus Walleij +Link: https://lore.kernel.org/r/a989b3b798ecaf3b45f35160e30e605636d66a77.1669044086.git.geert+renesas@glider.be +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/Kconfig ++++ b/drivers/usb/fotg210/Kconfig +@@ -4,6 +4,7 @@ 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 diff --git a/target/linux/gemini/patches-6.1/0008-fotg210-udc-Use-dev-pointer-in-probe-and-dev_message.patch b/target/linux/gemini/patches-6.1/0008-fotg210-udc-Use-dev-pointer-in-probe-and-dev_message.patch new file mode 100644 index 0000000000..2a595e885d --- /dev/null +++ b/target/linux/gemini/patches-6.1/0008-fotg210-udc-Use-dev-pointer-in-probe-and-dev_message.patch @@ -0,0 +1,61 @@ +From 27cd321a365fecac857e41ad1681062994142e4a Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 14 Nov 2022 12:51:58 +0100 +Subject: [PATCH 08/29] fotg210-udc: Use dev pointer in probe and dev_messages + +Add a local struct device *dev pointer and use dev_err() +etc to report status. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221114115201.302887-1-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1104,6 +1104,7 @@ int fotg210_udc_probe(struct platform_de + struct resource *res, *ires; + struct fotg210_udc *fotg210 = NULL; + struct fotg210_ep *_ep[FOTG210_MAX_NUM_EP]; ++ struct device *dev = &pdev->dev; + int ret = 0; + int i; + +@@ -1135,7 +1136,7 @@ int fotg210_udc_probe(struct platform_de + + fotg210->reg = ioremap(res->start, resource_size(res)); + if (fotg210->reg == NULL) { +- pr_err("ioremap error.\n"); ++ dev_err(dev, "ioremap error\n"); + goto err_alloc; + } + +@@ -1146,8 +1147,8 @@ int fotg210_udc_probe(struct platform_de + fotg210->gadget.ops = &fotg210_gadget_ops; + + fotg210->gadget.max_speed = USB_SPEED_HIGH; +- fotg210->gadget.dev.parent = &pdev->dev; +- fotg210->gadget.dev.dma_mask = pdev->dev.dma_mask; ++ 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); +@@ -1195,15 +1196,15 @@ int fotg210_udc_probe(struct platform_de + ret = request_irq(ires->start, fotg210_irq, IRQF_SHARED, + udc_name, fotg210); + if (ret < 0) { +- pr_err("request_irq error (%d)\n", ret); ++ dev_err(dev, "request_irq error (%d)\n", ret); + goto err_req; + } + +- ret = usb_add_gadget_udc(&pdev->dev, &fotg210->gadget); ++ ret = usb_add_gadget_udc(dev, &fotg210->gadget); + if (ret) + goto err_add_udc; + +- dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION); ++ dev_info(dev, "version %s\n", DRIVER_VERSION); + + return 0; + diff --git a/target/linux/gemini/patches-6.1/0009-fotg210-udc-Support-optional-external-PHY.patch b/target/linux/gemini/patches-6.1/0009-fotg210-udc-Support-optional-external-PHY.patch new file mode 100644 index 0000000000..498875c535 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0009-fotg210-udc-Support-optional-external-PHY.patch @@ -0,0 +1,158 @@ +From 03e4b585ac947e2d422bedf03179bbfec3aca3cf Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 14 Nov 2022 12:51:59 +0100 +Subject: [PATCH 09/29] fotg210-udc: Support optional external PHY + +This adds support for an optional external PHY to the FOTG210 +UDC driver. + +Tested with the GPIO VBUS PHY driver on the Gemini SoC. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221114115201.302887-2-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -15,6 +15,8 @@ + #include + #include + #include ++#include ++#include + + #include "fotg210.h" + #include "fotg210-udc.h" +@@ -1022,10 +1024,18 @@ static int fotg210_udc_start(struct usb_ + { + struct fotg210_udc *fotg210 = gadget_to_fotg210(g); + u32 value; ++ int ret; + + /* hook up the driver */ + fotg210->driver = driver; + ++ 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"); ++ } ++ + /* enable device global interrupt */ + value = ioread32(fotg210->reg + FOTG210_DMCR); + value |= DMCR_GLINT_EN; +@@ -1067,6 +1077,9 @@ static int fotg210_udc_stop(struct usb_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); +@@ -1082,12 +1095,50 @@ static const struct usb_gadget_ops fotg2 + .udc_stop = fotg210_udc_stop, + }; + ++/** ++ * 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); + +@@ -1127,6 +1178,22 @@ int fotg210_udc_probe(struct platform_de + if (fotg210 == NULL) + goto err; + ++ fotg210->dev = dev; ++ ++ fotg210->phy = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0); ++ if (IS_ERR(fotg210->phy)) { ++ ret = PTR_ERR(fotg210->phy); ++ if (ret == -EPROBE_DEFER) ++ goto err; ++ dev_info(dev, "no PHY found\n"); ++ fotg210->phy = NULL; ++ } else { ++ ret = usb_phy_init(fotg210->phy); ++ if (ret) ++ goto err; ++ dev_info(dev, "found and initialized PHY\n"); ++ } ++ + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { + _ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); + if (_ep[i] == NULL) +@@ -1200,6 +1267,9 @@ int fotg210_udc_probe(struct platform_de + 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; +@@ -1209,6 +1279,8 @@ int fotg210_udc_probe(struct platform_de + return 0; + + err_add_udc: ++ if (!IS_ERR_OR_NULL(fotg210->phy)) ++ usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier); + free_irq(ires->start, fotg210); + + err_req: +--- a/drivers/usb/fotg210/fotg210-udc.h ++++ b/drivers/usb/fotg210/fotg210-udc.h +@@ -234,6 +234,8 @@ struct fotg210_udc { + + unsigned long irq_trigger; + ++ struct device *dev; ++ struct usb_phy *phy; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + diff --git a/target/linux/gemini/patches-6.1/0010-fotg210-udc-Handle-PCLK.patch b/target/linux/gemini/patches-6.1/0010-fotg210-udc-Handle-PCLK.patch new file mode 100644 index 0000000000..8da3de3b47 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0010-fotg210-udc-Handle-PCLK.patch @@ -0,0 +1,90 @@ +From 772ea3ec2b9363b45ef9a4768ea205f758c3debc Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 14 Nov 2022 12:52:00 +0100 +Subject: [PATCH 10/29] fotg210-udc: Handle PCLK + +This adds optional handling of the peripheral clock PCLK. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221114115201.302887-3-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -15,6 +15,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -1145,6 +1146,10 @@ int fotg210_udc_remove(struct platform_d + fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) + kfree(fotg210->ep[i]); ++ ++ if (!IS_ERR(fotg210->pclk)) ++ clk_disable_unprepare(fotg210->pclk); ++ + kfree(fotg210); + + return 0; +@@ -1180,17 +1185,34 @@ int fotg210_udc_probe(struct platform_de + + fotg210->dev = dev; + ++ /* It's OK not to supply this clock */ ++ fotg210->pclk = devm_clk_get(dev, "PCLK"); ++ if (!IS_ERR(fotg210->pclk)) { ++ ret = clk_prepare_enable(fotg210->pclk); ++ if (ret) { ++ dev_err(dev, "failed to enable PCLK\n"); ++ return ret; ++ } ++ } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { ++ /* ++ * Percolate deferrals, for anything else, ++ * just live without the clocking. ++ */ ++ ret = -EPROBE_DEFER; ++ goto err; ++ } ++ + fotg210->phy = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0); + if (IS_ERR(fotg210->phy)) { + ret = PTR_ERR(fotg210->phy); + if (ret == -EPROBE_DEFER) +- goto err; ++ goto err_pclk; + dev_info(dev, "no PHY found\n"); + fotg210->phy = NULL; + } else { + ret = usb_phy_init(fotg210->phy); + if (ret) +- goto err; ++ goto err_pclk; + dev_info(dev, "found and initialized PHY\n"); + } + +@@ -1292,6 +1314,10 @@ err_map: + err_alloc: + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) + kfree(fotg210->ep[i]); ++err_pclk: ++ if (!IS_ERR(fotg210->pclk)) ++ clk_disable_unprepare(fotg210->pclk); ++ + kfree(fotg210); + + err: +--- a/drivers/usb/fotg210/fotg210-udc.h ++++ b/drivers/usb/fotg210/fotg210-udc.h +@@ -231,6 +231,7 @@ struct fotg210_ep { + struct fotg210_udc { + spinlock_t lock; /* protect the struct */ + void __iomem *reg; ++ struct clk *pclk; + + unsigned long irq_trigger; + diff --git a/target/linux/gemini/patches-6.1/0011-fotg210-udc-Get-IRQ-using-platform_get_irq.patch b/target/linux/gemini/patches-6.1/0011-fotg210-udc-Get-IRQ-using-platform_get_irq.patch new file mode 100644 index 0000000000..9544de7cb0 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0011-fotg210-udc-Get-IRQ-using-platform_get_irq.patch @@ -0,0 +1,69 @@ +From eda686d41e298a9d16708d2ec8d12d8e682dd7ca Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 14 Nov 2022 12:52:01 +0100 +Subject: [PATCH 11/29] fotg210-udc: Get IRQ using platform_get_irq() + +The platform_get_irq() is necessary to use to get dynamic +IRQ resolution when instantiating the device from the +device tree. IRQs are not passed as resources in that +case. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20221114115201.302887-4-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1157,10 +1157,11 @@ int fotg210_udc_remove(struct platform_d + + int fotg210_udc_probe(struct platform_device *pdev) + { +- struct resource *res, *ires; ++ struct resource *res; + struct fotg210_udc *fotg210 = NULL; + struct fotg210_ep *_ep[FOTG210_MAX_NUM_EP]; + struct device *dev = &pdev->dev; ++ int irq; + int ret = 0; + int i; + +@@ -1170,9 +1171,9 @@ int fotg210_udc_probe(struct platform_de + return -ENODEV; + } + +- ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +- if (!ires) { +- pr_err("platform_get_resource IORESOURCE_IRQ error.\n"); ++ irq = platform_get_irq(pdev, 0); ++ if (irq < 0) { ++ pr_err("could not get irq\n"); + return -ENODEV; + } + +@@ -1202,7 +1203,7 @@ int fotg210_udc_probe(struct platform_de + goto err; + } + +- fotg210->phy = devm_usb_get_phy_by_phandle(dev->parent, "usb-phy", 0); ++ 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) +@@ -1282,7 +1283,7 @@ int fotg210_udc_probe(struct platform_de + + fotg210_disable_unplug(fotg210); + +- ret = request_irq(ires->start, fotg210_irq, IRQF_SHARED, ++ ret = request_irq(irq, fotg210_irq, IRQF_SHARED, + udc_name, fotg210); + if (ret < 0) { + dev_err(dev, "request_irq error (%d)\n", ret); +@@ -1303,7 +1304,7 @@ int fotg210_udc_probe(struct platform_de + err_add_udc: + if (!IS_ERR_OR_NULL(fotg210->phy)) + usb_unregister_notifier(fotg210->phy, &fotg210_phy_notifier); +- free_irq(ires->start, fotg210); ++ free_irq(irq, fotg210); + + err_req: + fotg210_ep_free_request(&fotg210->ep[0]->ep, fotg210->ep0_req); diff --git a/target/linux/gemini/patches-6.1/0012-usb-fotg210-udc-Remove-a-useless-assignment.patch b/target/linux/gemini/patches-6.1/0012-usb-fotg210-udc-Remove-a-useless-assignment.patch new file mode 100644 index 0000000000..8c33c50b2c --- /dev/null +++ b/target/linux/gemini/patches-6.1/0012-usb-fotg210-udc-Remove-a-useless-assignment.patch @@ -0,0 +1,39 @@ +From 7889a2f0256c55e0184dffd0001d0782f9e4cb83 Mon Sep 17 00:00:00 2001 +From: Christophe JAILLET +Date: Mon, 14 Nov 2022 21:38:04 +0100 +Subject: [PATCH 12/29] usb: fotg210-udc: Remove a useless assignment + +There is no need to use an intermediate array for these memory allocations, +so, axe it. + +While at it, turn a '== NULL' into a shorter '!' when testing memory +allocation failure. + +Signed-off-by: Christophe JAILLET +Reviewed-by: Linus Walleij +Link: https://lore.kernel.org/r/deab9696fc4000499470e7ccbca7c36fca17bd4e.1668458274.git.christophe.jaillet@wanadoo.fr +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1159,7 +1159,6 @@ int fotg210_udc_probe(struct platform_de + { + struct resource *res; + struct fotg210_udc *fotg210 = NULL; +- struct fotg210_ep *_ep[FOTG210_MAX_NUM_EP]; + struct device *dev = &pdev->dev; + int irq; + int ret = 0; +@@ -1218,10 +1217,9 @@ int fotg210_udc_probe(struct platform_de + } + + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { +- _ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); +- if (_ep[i] == NULL) ++ fotg210->ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); ++ if (!fotg210->ep[i]) + goto err_alloc; +- fotg210->ep[i] = _ep[i]; + } + + fotg210->reg = ioremap(res->start, resource_size(res)); diff --git a/target/linux/gemini/patches-6.1/0013-usb-fotg210-udc-fix-potential-memory-leak-in-fotg210.patch b/target/linux/gemini/patches-6.1/0013-usb-fotg210-udc-fix-potential-memory-leak-in-fotg210.patch new file mode 100644 index 0000000000..178135662f --- /dev/null +++ b/target/linux/gemini/patches-6.1/0013-usb-fotg210-udc-fix-potential-memory-leak-in-fotg210.patch @@ -0,0 +1,58 @@ +From 7b95ade85ac18eec63e81ac58a482b3e88361ffd Mon Sep 17 00:00:00 2001 +From: Yi Yang +Date: Fri, 2 Dec 2022 09:21:26 +0800 +Subject: [PATCH 13/29] usb: fotg210-udc: fix potential memory leak in + fotg210_udc_probe() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +In fotg210_udc_probe(), if devm_clk_get() or clk_prepare_enable() +fails, 'fotg210' will not be freed, which will lead to a memory leak. +Fix it by moving kfree() to a proper location. + +In addition,we can use "return -ENOMEM" instead of "goto err" +to simplify the code. + +Fixes: 718a38d092ec ("fotg210-udc: Handle PCLK") +Reviewed-by: Andrzej Pietrasiewicz +Reviewed-by: Linus Walleij +Signed-off-by: Yi Yang +Link: https://lore.kernel.org/r/20221202012126.246953-1-yiyang13@huawei.com +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1176,12 +1176,10 @@ int fotg210_udc_probe(struct platform_de + return -ENODEV; + } + +- ret = -ENOMEM; +- + /* initialize udc */ + fotg210 = kzalloc(sizeof(struct fotg210_udc), GFP_KERNEL); + if (fotg210 == NULL) +- goto err; ++ return -ENOMEM; + + fotg210->dev = dev; + +@@ -1191,7 +1189,7 @@ int fotg210_udc_probe(struct platform_de + ret = clk_prepare_enable(fotg210->pclk); + if (ret) { + dev_err(dev, "failed to enable PCLK\n"); +- return ret; ++ goto err; + } + } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { + /* +@@ -1317,8 +1315,7 @@ err_pclk: + if (!IS_ERR(fotg210->pclk)) + clk_disable_unprepare(fotg210->pclk); + +- kfree(fotg210); +- + err: ++ kfree(fotg210); + return ret; + } diff --git a/target/linux/gemini/patches-6.1/0014-usb-fotg210-fix-OTG-only-build.patch b/target/linux/gemini/patches-6.1/0014-usb-fotg210-fix-OTG-only-build.patch new file mode 100644 index 0000000000..acdf1796f3 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0014-usb-fotg210-fix-OTG-only-build.patch @@ -0,0 +1,39 @@ +From d8eed400495029ba551704ff0fae1dad87332291 Mon Sep 17 00:00:00 2001 +From: Arnd Bergmann +Date: Thu, 15 Dec 2022 17:57:20 +0100 +Subject: [PATCH 14/29] usb: fotg210: fix OTG-only build + +The fotg210 module combines the HCD and OTG drivers, which then +fails to build when only the USB gadget support is enabled +in the kernel but host support is not: + +aarch64-linux-ld: drivers/usb/fotg210/fotg210-core.o: in function `fotg210_init': +fotg210-core.c:(.init.text+0xc): undefined reference to `usb_disabled' + +Move the check for usb_disabled() after the check for the HCD module, +and let the OTG driver still be probed in this configuration. + +A nicer approach might be to have the common portion built as a +library module, with the two platform other files registering +their own platform_driver instances separately. + +Fixes: ddacd6ef44ca ("usb: fotg210: Fix Kconfig for USB host modules") +Reviewed-by: Linus Walleij +Signed-off-by: Arnd Bergmann +Link: https://lore.kernel.org/r/20221215165728.2062984-1-arnd@kernel.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -144,10 +144,7 @@ static struct platform_driver fotg210_dr + + static int __init fotg210_init(void) + { +- if (usb_disabled()) +- return -ENODEV; +- +- if (IS_ENABLED(CONFIG_USB_FOTG210_HCD)) ++ if (IS_ENABLED(CONFIG_USB_FOTG210_HCD) && !usb_disabled()) + fotg210_hcd_init(); + return platform_driver_register(&fotg210_driver); + } diff --git a/target/linux/gemini/patches-6.1/0015-usb-fotg210-udc-fix-error-return-code-in-fotg210_udc.patch b/target/linux/gemini/patches-6.1/0015-usb-fotg210-udc-fix-error-return-code-in-fotg210_udc.patch new file mode 100644 index 0000000000..a9bbca58b4 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0015-usb-fotg210-udc-fix-error-return-code-in-fotg210_udc.patch @@ -0,0 +1,28 @@ +From eaaa85d907fe27852dd960b2bc5d7bcf11bc3ebd Mon Sep 17 00:00:00 2001 +From: Yang Yingliang +Date: Fri, 30 Dec 2022 14:54:27 +0800 +Subject: [PATCH 15/29] usb: fotg210-udc: fix error return code in + fotg210_udc_probe() + +After commit 5f217ccd520f ("fotg210-udc: Support optional external PHY"), +the error code is re-assigned to 0 in fotg210_udc_probe(), if allocate or +map memory fails after the assignment, it can't return an error code. Set +the error code to -ENOMEM to fix this problem. + +Fixes: 5f217ccd520f ("fotg210-udc: Support optional external PHY") +Signed-off-by: Yang Yingliang +Reviewed-by: Linus Walleij +Link: https://lore.kernel.org/r/20221230065427.944586-1-yangyingliang@huawei.com +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1214,6 +1214,8 @@ int fotg210_udc_probe(struct platform_de + dev_info(dev, "found and initialized PHY\n"); + } + ++ ret = -ENOMEM; ++ + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) { + fotg210->ep[i] = kzalloc(sizeof(struct fotg210_ep), GFP_KERNEL); + if (!fotg210->ep[i]) diff --git a/target/linux/gemini/patches-6.1/0016-usb-fotg210-List-different-variants.patch b/target/linux/gemini/patches-6.1/0016-usb-fotg210-List-different-variants.patch new file mode 100644 index 0000000000..6ff6d28ad3 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0016-usb-fotg210-List-different-variants.patch @@ -0,0 +1,25 @@ +From 407577548b2fcd41cc72ee05df1f05a430ed30a0 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 18 Jan 2023 08:09:16 +0100 +Subject: [PATCH 16/29] usb: fotg210: List different variants + +There are at least two variants of the FOTG: FOTG200 and +FOTG210. Handle them in this driver and let's add +more quirks as we go along. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-2-100388af9810@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -127,7 +127,9 @@ static int fotg210_remove(struct platfor + + #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); diff --git a/target/linux/gemini/patches-6.1/0017-usb-fotg210-Acquire-memory-resource-in-core.patch b/target/linux/gemini/patches-6.1/0017-usb-fotg210-Acquire-memory-resource-in-core.patch new file mode 100644 index 0000000000..7dbd511ecb --- /dev/null +++ b/target/linux/gemini/patches-6.1/0017-usb-fotg210-Acquire-memory-resource-in-core.patch @@ -0,0 +1,245 @@ +From fa735ad1afeb5791d5562617b9bbed74574d3e81 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 18 Jan 2023 08:09:17 +0100 +Subject: [PATCH 17/29] usb: fotg210: Acquire memory resource in core + +The subdrivers are obtaining and mapping the memory resource +separately. Create a common state container for the shared +resources and start populating this by acquiring the IO +memory resource and remap it and pass this to the subdrivers +for host and peripheral. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-3-100388af9810@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -33,9 +33,10 @@ + #define GEMINI_MISC_USB0_MINI_B BIT(29) + #define GEMINI_MISC_USB1_MINI_B BIT(30) + +-static int fotg210_gemini_init(struct device *dev, struct resource *res, ++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; +@@ -47,6 +48,7 @@ static int fotg210_gemini_init(struct de + dev_err(dev, "no syscon\n"); + return PTR_ERR(map); + } ++ fotg->map = map; + wakeup = of_property_read_bool(np, "wakeup-source"); + + /* +@@ -55,6 +57,7 @@ static int fotg210_gemini_init(struct de + */ + 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) +@@ -64,6 +67,7 @@ static int fotg210_gemini_init(struct de + 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) +@@ -89,23 +93,34 @@ static int fotg210_probe(struct platform + { + struct device *dev = &pdev->dev; + enum usb_dr_mode mode; ++ struct fotg210 *fotg; + int ret; + ++ fotg = devm_kzalloc(dev, sizeof(*fotg), GFP_KERNEL); ++ if (!fotg) ++ return -ENOMEM; ++ fotg->dev = dev; ++ ++ fotg->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!fotg->res) ++ return -ENODEV; ++ ++ fotg->base = devm_ioremap_resource(dev, fotg->res); ++ if (!fotg->base) ++ return -ENOMEM; ++ + mode = usb_get_dr_mode(dev); + + if (of_device_is_compatible(dev->of_node, "cortina,gemini-usb")) { +- struct resource *res; +- +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- ret = fotg210_gemini_init(dev, res, mode); ++ ret = fotg210_gemini_init(fotg, fotg->res, mode); + if (ret) + return ret; + } + + if (mode == USB_DR_MODE_PERIPHERAL) +- ret = fotg210_udc_probe(pdev); ++ ret = fotg210_udc_probe(pdev, fotg); + else +- ret = fotg210_hcd_probe(pdev); ++ ret = fotg210_hcd_probe(pdev, fotg); + + return ret; + } +--- a/drivers/usb/fotg210/fotg210-hcd.c ++++ b/drivers/usb/fotg210/fotg210-hcd.c +@@ -5557,11 +5557,10 @@ static void fotg210_init(struct fotg210_ + * 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) ++int fotg210_hcd_probe(struct platform_device *pdev, struct fotg210 *fotg) + { + struct device *dev = &pdev->dev; + struct usb_hcd *hcd; +- struct resource *res; + int irq; + int retval; + struct fotg210_hcd *fotg210; +@@ -5585,18 +5584,14 @@ int fotg210_hcd_probe(struct platform_de + + hcd->has_tt = 1; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- hcd->regs = devm_ioremap_resource(&pdev->dev, res); +- if (IS_ERR(hcd->regs)) { +- retval = PTR_ERR(hcd->regs); +- goto failed_put_hcd; +- } ++ hcd->regs = fotg->base; + +- hcd->rsrc_start = res->start; +- hcd->rsrc_len = resource_size(res); ++ 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; + + /* It's OK not to supply this clock */ +--- a/drivers/usb/fotg210/fotg210-hcd.h ++++ b/drivers/usb/fotg210/fotg210-hcd.h +@@ -182,6 +182,7 @@ struct fotg210_hcd { /* one per contro + # define INCR(x) do {} while (0) + #endif + ++ struct fotg210 *fotg; /* Overarching FOTG210 device */ + /* silicon clock */ + struct clk *pclk; + }; +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1155,21 +1155,14 @@ int fotg210_udc_remove(struct platform_d + return 0; + } + +-int fotg210_udc_probe(struct platform_device *pdev) ++int fotg210_udc_probe(struct platform_device *pdev, struct fotg210 *fotg) + { +- struct resource *res; + struct fotg210_udc *fotg210 = NULL; + struct device *dev = &pdev->dev; + int irq; + int ret = 0; + int i; + +- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); +- if (!res) { +- pr_err("platform_get_resource error.\n"); +- return -ENODEV; +- } +- + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + pr_err("could not get irq\n"); +@@ -1182,6 +1175,7 @@ int fotg210_udc_probe(struct platform_de + return -ENOMEM; + + fotg210->dev = dev; ++ fotg210->fotg = fotg; + + /* It's OK not to supply this clock */ + fotg210->pclk = devm_clk_get(dev, "PCLK"); +@@ -1222,11 +1216,7 @@ int fotg210_udc_probe(struct platform_de + goto err_alloc; + } + +- fotg210->reg = ioremap(res->start, resource_size(res)); +- if (fotg210->reg == NULL) { +- dev_err(dev, "ioremap error\n"); +- goto err_alloc; +- } ++ fotg210->reg = fotg->base; + + spin_lock_init(&fotg210->lock); + +--- a/drivers/usb/fotg210/fotg210-udc.h ++++ b/drivers/usb/fotg210/fotg210-udc.h +@@ -236,6 +236,7 @@ struct fotg210_udc { + unsigned long irq_trigger; + + struct device *dev; ++ struct fotg210 *fotg; + struct usb_phy *phy; + struct usb_gadget gadget; + struct usb_gadget_driver *driver; +--- a/drivers/usb/fotg210/fotg210.h ++++ b/drivers/usb/fotg210/fotg210.h +@@ -2,13 +2,28 @@ + #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 regmap *map; ++ enum gemini_port port; ++}; ++ + #ifdef CONFIG_USB_FOTG210_HCD +-int fotg210_hcd_probe(struct platform_device *pdev); ++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) ++static inline int fotg210_hcd_probe(struct platform_device *pdev, ++ struct fotg210 *fotg) + { + return 0; + } +@@ -26,10 +41,11 @@ static inline void fotg210_hcd_cleanup(v + #endif + + #ifdef CONFIG_USB_FOTG210_UDC +-int fotg210_udc_probe(struct platform_device *pdev); ++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) ++static inline int fotg210_udc_probe(struct platform_device *pdev, ++ struct fotg210 *fotg) + { + return 0; + } diff --git a/target/linux/gemini/patches-6.1/0018-usb-fotg210-Move-clock-handling-to-core.patch b/target/linux/gemini/patches-6.1/0018-usb-fotg210-Move-clock-handling-to-core.patch new file mode 100644 index 0000000000..9894f4dc66 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0018-usb-fotg210-Move-clock-handling-to-core.patch @@ -0,0 +1,196 @@ +From fb8e1e8dbc47e7aff7624b47adaa0a84d2983802 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 18 Jan 2023 08:09:18 +0100 +Subject: [PATCH 18/29] usb: fotg210: Move clock handling to core + +Grab the optional silicon block clock, prepare and enable it in +the core before proceeding to prepare the host or peripheral +driver. This saves duplicate code and also uses the simple +devm_clk_get_optional_enabled() to do everything we really +want to do. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-4-100388af9810@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -6,6 +6,7 @@ + * driver. + */ + #include ++#include + #include + #include + #include +@@ -109,6 +110,10 @@ static int fotg210_probe(struct platform + if (!fotg->base) + return -ENOMEM; + ++ 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")) { +--- a/drivers/usb/fotg210/fotg210-hcd.c ++++ b/drivers/usb/fotg210/fotg210-hcd.c +@@ -33,7 +33,6 @@ + #include + #include + #include +-#include + + #include + #include +@@ -5594,44 +5593,22 @@ int fotg210_hcd_probe(struct platform_de + fotg210->fotg = fotg; + fotg210->caps = hcd->regs; + +- /* It's OK not to supply this clock */ +- fotg210->pclk = clk_get(dev, "PCLK"); +- if (!IS_ERR(fotg210->pclk)) { +- retval = clk_prepare_enable(fotg210->pclk); +- if (retval) { +- dev_err(dev, "failed to enable PCLK\n"); +- goto failed_put_hcd; +- } +- } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { +- /* +- * Percolate deferrals, for anything else, +- * just live without the clocking. +- */ +- retval = PTR_ERR(fotg210->pclk); +- goto failed_dis_clk; +- } +- + retval = fotg210_setup(hcd); + if (retval) +- goto failed_dis_clk; ++ goto failed_put_hcd; + + fotg210_init(fotg210); + + retval = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (retval) { + dev_err(dev, "failed to add hcd with err %d\n", retval); +- goto failed_dis_clk; ++ goto failed_put_hcd; + } + device_wakeup_enable(hcd->self.controller); + platform_set_drvdata(pdev, hcd); + + return retval; + +-failed_dis_clk: +- if (!IS_ERR(fotg210->pclk)) { +- clk_disable_unprepare(fotg210->pclk); +- clk_put(fotg210->pclk); +- } + failed_put_hcd: + usb_put_hcd(hcd); + fail_create_hcd: +@@ -5647,12 +5624,6 @@ fail_create_hcd: + int fotg210_hcd_remove(struct platform_device *pdev) + { + struct usb_hcd *hcd = platform_get_drvdata(pdev); +- struct fotg210_hcd *fotg210 = hcd_to_fotg210(hcd); +- +- if (!IS_ERR(fotg210->pclk)) { +- clk_disable_unprepare(fotg210->pclk); +- clk_put(fotg210->pclk); +- } + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -15,7 +15,6 @@ + #include + #include + #include +-#include + #include + #include + +@@ -1147,9 +1146,6 @@ int fotg210_udc_remove(struct platform_d + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) + kfree(fotg210->ep[i]); + +- if (!IS_ERR(fotg210->pclk)) +- clk_disable_unprepare(fotg210->pclk); +- + kfree(fotg210); + + return 0; +@@ -1177,34 +1173,17 @@ int fotg210_udc_probe(struct platform_de + fotg210->dev = dev; + fotg210->fotg = fotg; + +- /* It's OK not to supply this clock */ +- fotg210->pclk = devm_clk_get(dev, "PCLK"); +- if (!IS_ERR(fotg210->pclk)) { +- ret = clk_prepare_enable(fotg210->pclk); +- if (ret) { +- dev_err(dev, "failed to enable PCLK\n"); +- goto err; +- } +- } else if (PTR_ERR(fotg210->pclk) == -EPROBE_DEFER) { +- /* +- * Percolate deferrals, for anything else, +- * just live without the clocking. +- */ +- ret = -EPROBE_DEFER; +- goto err; +- } +- + 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_pclk; ++ goto err_free; + dev_info(dev, "no PHY found\n"); + fotg210->phy = NULL; + } else { + ret = usb_phy_init(fotg210->phy); + if (ret) +- goto err_pclk; ++ goto err_free; + dev_info(dev, "found and initialized PHY\n"); + } + +@@ -1303,11 +1282,8 @@ err_map: + err_alloc: + for (i = 0; i < FOTG210_MAX_NUM_EP; i++) + kfree(fotg210->ep[i]); +-err_pclk: +- if (!IS_ERR(fotg210->pclk)) +- clk_disable_unprepare(fotg210->pclk); + +-err: ++err_free: + kfree(fotg210); + return ret; + } +--- a/drivers/usb/fotg210/fotg210-udc.h ++++ b/drivers/usb/fotg210/fotg210-udc.h +@@ -231,7 +231,6 @@ struct fotg210_ep { + struct fotg210_udc { + spinlock_t lock; /* protect the struct */ + void __iomem *reg; +- struct clk *pclk; + + unsigned long irq_trigger; + +--- a/drivers/usb/fotg210/fotg210.h ++++ b/drivers/usb/fotg210/fotg210.h +@@ -12,6 +12,7 @@ struct fotg210 { + struct device *dev; + struct resource *res; + void __iomem *base; ++ struct clk *pclk; + struct regmap *map; + enum gemini_port port; + }; diff --git a/target/linux/gemini/patches-6.1/0019-usb-fotg210-Check-role-register-in-core.patch b/target/linux/gemini/patches-6.1/0019-usb-fotg210-Check-role-register-in-core.patch new file mode 100644 index 0000000000..892b0d31af --- /dev/null +++ b/target/linux/gemini/patches-6.1/0019-usb-fotg210-Check-role-register-in-core.patch @@ -0,0 +1,54 @@ +From b1b07abb598211de3ce7f52abdf8dcb24384341e Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 18 Jan 2023 08:09:19 +0100 +Subject: [PATCH 19/29] usb: fotg210: Check role register in core + +Read the role register and check that we are in host/peripheral +mode and issue warnings if we're not in the right role when +probing respective driver. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-5-100388af9810@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -18,6 +18,11 @@ + + #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. +@@ -95,6 +100,7 @@ static int fotg210_probe(struct platform + 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); +@@ -122,10 +128,16 @@ static int fotg210_probe(struct platform + return ret; + } + +- if (mode == USB_DR_MODE_PERIPHERAL) ++ 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 ++ } else { ++ if (val & FOTG210_RR_CROLE) ++ dev_err(dev, "block not in host role\n"); + ret = fotg210_hcd_probe(pdev, fotg); ++ } + + return ret; + } diff --git a/target/linux/gemini/patches-6.1/0020-usb-fotg210-udc-Assign-of_node-and-speed-on-start.patch b/target/linux/gemini/patches-6.1/0020-usb-fotg210-udc-Assign-of_node-and-speed-on-start.patch new file mode 100644 index 0000000000..20f8f94350 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0020-usb-fotg210-udc-Assign-of_node-and-speed-on-start.patch @@ -0,0 +1,34 @@ +From d7c2b0b6da75b86cf5ddbcd51a74d74e19bbf178 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 18 Jan 2023 08:09:20 +0100 +Subject: [PATCH 20/29] usb: fotg210-udc: Assign of_node and speed on start + +Follow the example set by other drivers to assign of_node +and speed to the driver when binding, also print bound +info akin to other UDC drivers. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-6-100388af9810@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1028,6 +1028,10 @@ static int fotg210_udc_start(struct usb_ + + /* 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, +@@ -1084,6 +1088,7 @@ static int fotg210_udc_stop(struct usb_g + + fotg210_init(fotg210); + fotg210->driver = NULL; ++ fotg210->gadget.speed = USB_SPEED_UNKNOWN; + + spin_unlock_irqrestore(&fotg210->lock, flags); + diff --git a/target/linux/gemini/patches-6.1/0021-usb-fotg210-udc-Implement-VBUS-session.patch b/target/linux/gemini/patches-6.1/0021-usb-fotg210-udc-Implement-VBUS-session.patch new file mode 100644 index 0000000000..d98561f0d4 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0021-usb-fotg210-udc-Implement-VBUS-session.patch @@ -0,0 +1,96 @@ +From 2fbbfb2c556944945639b17b13fcb1e05272b646 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Wed, 18 Jan 2023 08:09:21 +0100 +Subject: [PATCH 21/29] usb: fotg210-udc: Implement VBUS session + +Implement VBUS session handling for FOTG210. This is +mainly used by the UDC driver which needs to call down to +the FOTG210 core and enable/disable VBUS, as this needs to be +handled outside of the HCD and UDC drivers, by platform +specific glue code. + +The Gemini has a special bit in a system register to turn +VBUS on and off so we implement this in the FOTG210 core. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230103-gemini-fotg210-usb-v2-7-100388af9810@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-core.c ++++ b/drivers/usb/fotg210/fotg210-core.c +@@ -95,6 +95,35 @@ static int fotg210_gemini_init(struct fo + return 0; + } + ++/** ++ * fotg210_vbus() - Called by gadget driver to enable/disable VBUS ++ * @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", ++ enable ? "enable" : "disable"); ++ dev_info(fotg->dev, "%s: %s VBUS\n", __func__, enable ? "enable" : "disable"); ++} ++ + static int fotg210_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -1095,9 +1095,26 @@ static int fotg210_udc_stop(struct usb_g + return 0; + } + ++/** ++ * fotg210_vbus_session - Called by external transceiver to enable/disable udc ++ * @_gadget: 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, + }; + + /** +--- a/drivers/usb/fotg210/fotg210.h ++++ b/drivers/usb/fotg210/fotg210.h +@@ -17,6 +17,8 @@ struct fotg210 { + 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); diff --git a/target/linux/gemini/patches-6.1/0022-fotg210-udc-Introduce-and-use-a-fotg210_ack_int-func.patch b/target/linux/gemini/patches-6.1/0022-fotg210-udc-Introduce-and-use-a-fotg210_ack_int-func.patch new file mode 100644 index 0000000000..fc5831eb23 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0022-fotg210-udc-Introduce-and-use-a-fotg210_ack_int-func.patch @@ -0,0 +1,134 @@ +From f011d1eab23f4c063c5441c0d5a22898adf9145c Mon Sep 17 00:00:00 2001 +From: Fabian Vogt +Date: Mon, 23 Jan 2023 08:35:07 +0100 +Subject: [PATCH 22/29] fotg210-udc: Introduce and use a fotg210_ack_int + function + +This is in preparation of support for devices where interrupts are acked +differently. + +Signed-off-by: Fabian Vogt +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230123073508.2350402-3-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -28,6 +28,14 @@ static const char udc_name[] = "fotg210_ + 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); +@@ -303,8 +311,7 @@ static void fotg210_wait_dma_done(struct + goto dma_reset; + } while (!(value & DISGR2_DMA_CMPLT)); + +- value &= ~DISGR2_DMA_CMPLT; +- iowrite32(value, ep->fotg210->reg + FOTG210_DISGR2); ++ fotg210_ack_int(ep->fotg210, FOTG210_DISGR2, DISGR2_DMA_CMPLT); + return; + + dma_reset: +@@ -844,14 +851,6 @@ static void fotg210_ep0in(struct fotg210 + } + } + +-static void fotg210_clear_comabt_int(struct fotg210_udc *fotg210) +-{ +- u32 value = ioread32(fotg210->reg + FOTG210_DISGR0); +- +- value &= ~DISGR0_CX_COMABT_INT; +- iowrite32(value, fotg210->reg + FOTG210_DISGR0); +-} +- + static void fotg210_in_fifo_handler(struct fotg210_ep *ep) + { + struct fotg210_request *req = list_entry(ep->queue.next, +@@ -893,60 +892,43 @@ static irqreturn_t fotg210_irq(int irq, + void __iomem *reg = fotg210->reg + FOTG210_DISGR2; + u32 int_grp2 = ioread32(reg); + u32 int_msk2 = ioread32(fotg210->reg + FOTG210_DMISGR2); +- u32 value; + + int_grp2 &= ~int_msk2; + + if (int_grp2 & DISGR2_USBRST_INT) { + usb_gadget_udc_reset(&fotg210->gadget, + fotg210->driver); +- value = ioread32(reg); +- value &= ~DISGR2_USBRST_INT; +- iowrite32(value, reg); ++ fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_USBRST_INT); + pr_info("fotg210 udc reset\n"); + } + if (int_grp2 & DISGR2_SUSP_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_SUSP_INT; +- iowrite32(value, reg); ++ fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_SUSP_INT); + pr_info("fotg210 udc suspend\n"); + } + if (int_grp2 & DISGR2_RESM_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_RESM_INT; +- iowrite32(value, reg); ++ fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_RESM_INT); + pr_info("fotg210 udc resume\n"); + } + if (int_grp2 & DISGR2_ISO_SEQ_ERR_INT) { +- value = ioread32(reg); +- value &= ~DISGR2_ISO_SEQ_ERR_INT; +- iowrite32(value, reg); ++ 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) { +- value = ioread32(reg); +- value &= ~DISGR2_ISO_SEQ_ABORT_INT; +- iowrite32(value, reg); ++ 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); +- value = ioread32(reg); +- value &= ~DISGR2_TX0BYTE_INT; +- iowrite32(value, reg); ++ 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); +- value = ioread32(reg); +- value &= ~DISGR2_RX0BYTE_INT; +- iowrite32(value, reg); ++ fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_RX0BYTE_INT); + pr_info("fotg210 received 0 byte\n"); + } + if (int_grp2 & DISGR2_DMA_ERROR) { +- value = ioread32(reg); +- value &= ~DISGR2_DMA_ERROR; +- iowrite32(value, reg); ++ fotg210_ack_int(fotg210, FOTG210_DISGR2, DISGR2_DMA_ERROR); + } + } + +@@ -960,7 +942,7 @@ static irqreturn_t fotg210_irq(int irq, + + /* the highest priority in this source register */ + if (int_grp0 & DISGR0_CX_COMABT_INT) { +- fotg210_clear_comabt_int(fotg210); ++ fotg210_ack_int(fotg210, FOTG210_DISGR0, DISGR0_CX_COMABT_INT); + pr_info("fotg210 CX command abort\n"); + } + diff --git a/target/linux/gemini/patches-6.1/0023-fotg210-udc-Improve-device-initialization.patch b/target/linux/gemini/patches-6.1/0023-fotg210-udc-Improve-device-initialization.patch new file mode 100644 index 0000000000..fde17a48b3 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0023-fotg210-udc-Improve-device-initialization.patch @@ -0,0 +1,62 @@ +From 367747c7813cecf19b46ef7134691f903ab76dc9 Mon Sep 17 00:00:00 2001 +From: Fabian Vogt +Date: Mon, 23 Jan 2023 08:35:08 +0100 +Subject: [PATCH 23/29] fotg210-udc: Improve device initialization + +Reset the device explicitly to get into a known state and also set the chip +enable bit. Additionally, mask interrupts which aren't handled. + +Signed-off-by: Fabian Vogt +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230123073508.2350402-4-linus.walleij@linaro.org +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-udc.c ++++ b/drivers/usb/fotg210/fotg210-udc.c +@@ -7,6 +7,7 @@ + * Author : Yuan-Hsin Chen + */ + ++#include + #include + #include + #include +@@ -1022,6 +1023,11 @@ static int fotg210_udc_start(struct usb_ + 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; +@@ -1038,6 +1044,15 @@ static void fotg210_init(struct fotg210_ + 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; +--- a/drivers/usb/fotg210/fotg210-udc.h ++++ b/drivers/usb/fotg210/fotg210-udc.h +@@ -58,6 +58,8 @@ + + /* 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) */ diff --git a/target/linux/gemini/patches-6.1/0024-usb-fotg210-hcd-use-sysfs_emit-to-instead-of-scnprin.patch b/target/linux/gemini/patches-6.1/0024-usb-fotg210-hcd-use-sysfs_emit-to-instead-of-scnprin.patch new file mode 100644 index 0000000000..680836110a --- /dev/null +++ b/target/linux/gemini/patches-6.1/0024-usb-fotg210-hcd-use-sysfs_emit-to-instead-of-scnprin.patch @@ -0,0 +1,32 @@ +From 482830a70408a5d30af264b3d6706f818c78b2b2 Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 20 Jan 2023 17:44:33 +0200 +Subject: [PATCH 24/29] usb: fotg210-hcd: use sysfs_emit() to instead of + scnprintf() + +Follow the advice of the Documentation/filesystems/sysfs.rst and show() +should only use sysfs_emit() or sysfs_emit_at() when formatting the +value to be returned to user space. + +Signed-off-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20230120154437.22025-1-andriy.shevchenko@linux.intel.com +Signed-off-by: Greg Kroah-Hartman +--- +--- a/drivers/usb/fotg210/fotg210-hcd.c ++++ b/drivers/usb/fotg210/fotg210-hcd.c +@@ -4686,14 +4686,11 @@ static ssize_t uframe_periodic_max_show( + struct device_attribute *attr, char *buf) + { + struct fotg210_hcd *fotg210; +- int n; + + fotg210 = hcd_to_fotg210(bus_to_hcd(dev_get_drvdata(dev))); +- n = scnprintf(buf, PAGE_SIZE, "%d\n", fotg210->uframe_periodic_max); +- return n; ++ 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) + { diff --git a/target/linux/gemini/patches-6.1/0025-ARM-dts-gemini-Push-down-flash-address-size-cells.patch b/target/linux/gemini/patches-6.1/0025-ARM-dts-gemini-Push-down-flash-address-size-cells.patch new file mode 100644 index 0000000000..1e031f1d4f --- /dev/null +++ b/target/linux/gemini/patches-6.1/0025-ARM-dts-gemini-Push-down-flash-address-size-cells.patch @@ -0,0 +1,62 @@ +From 6b84aa39a063eec883d410a9893cec70fce56163 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Sun, 4 Dec 2022 20:02:28 +0100 +Subject: [PATCH 25/29] ARM: dts: gemini: Push down flash address/size cells + +The platforms not defining any OF partions complain like +this: + +../arch/arm/boot/dts/gemini.dtsi:19.25-28.5: Warning + (avoid_unnecessary_addr_size): /soc/flash@30000000: unnecessary + #address-cells/#size-cells without "ranges" or child "reg" property + +Get rid of this by only defining the address-cells and +size-cells where it is actually used by OF partitions. + +Link: https://lore.kernel.org/r/20221204190230.3345590-1-linus.walleij@linaro.org +Signed-off-by: Linus Walleij +--- +--- a/arch/arm/boot/dts/gemini-dlink-dns-313.dts ++++ b/arch/arm/boot/dts/gemini-dlink-dns-313.dts +@@ -164,6 +164,8 @@ + compatible = "cortina,gemini-flash", "jedec-flash"; + status = "okay"; + reg = <0x30000000 0x00080000>; ++ #address-cells = <1>; ++ #size-cells = <1>; + + /* + * This "RedBoot" is the Storlink derivative. +--- a/arch/arm/boot/dts/gemini-wbd111.dts ++++ b/arch/arm/boot/dts/gemini-wbd111.dts +@@ -86,6 +86,8 @@ + status = "okay"; + /* 8MB of flash */ + reg = <0x30000000 0x00800000>; ++ #address-cells = <1>; ++ #size-cells = <1>; + + partition@0 { + label = "RedBoot"; +--- a/arch/arm/boot/dts/gemini-wbd222.dts ++++ b/arch/arm/boot/dts/gemini-wbd222.dts +@@ -90,6 +90,8 @@ + status = "okay"; + /* 8MB of flash */ + reg = <0x30000000 0x00800000>; ++ #address-cells = <1>; ++ #size-cells = <1>; + + partition@0 { + label = "RedBoot"; +--- a/arch/arm/boot/dts/gemini.dtsi ++++ b/arch/arm/boot/dts/gemini.dtsi +@@ -22,8 +22,6 @@ + pinctrl-names = "default"; + pinctrl-0 = <&pflash_default_pins>; + bank-width = <2>; +- #address-cells = <1>; +- #size-cells = <1>; + status = "disabled"; + }; + diff --git a/target/linux/gemini/patches-6.1/0026-ARM-dts-gemini-wbd111-Use-RedBoot-partion-parser.patch b/target/linux/gemini/patches-6.1/0026-ARM-dts-gemini-wbd111-Use-RedBoot-partion-parser.patch new file mode 100644 index 0000000000..1aff23ed1b --- /dev/null +++ b/target/linux/gemini/patches-6.1/0026-ARM-dts-gemini-wbd111-Use-RedBoot-partion-parser.patch @@ -0,0 +1,54 @@ +From 0e733f5af628210f372585e431504a7024e7b571 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Sun, 4 Dec 2022 20:02:29 +0100 +Subject: [PATCH 26/29] ARM: dts: gemini: wbd111: Use RedBoot partion parser + +This is clearly a RedBoot partitioned device with 0x20000 +sized erase blocks. + +Link: https://lore.kernel.org/r/20221204190230.3345590-2-linus.walleij@linaro.org +Signed-off-by: Linus Walleij +--- +--- a/arch/arm/boot/dts/gemini-wbd111.dts ++++ b/arch/arm/boot/dts/gemini-wbd111.dts +@@ -86,36 +86,11 @@ + status = "okay"; + /* 8MB of flash */ + reg = <0x30000000 0x00800000>; +- #address-cells = <1>; +- #size-cells = <1>; + +- partition@0 { +- label = "RedBoot"; +- reg = <0x00000000 0x00020000>; +- read-only; +- }; +- partition@20000 { +- label = "kernel"; +- reg = <0x00020000 0x00100000>; +- }; +- partition@120000 { +- label = "rootfs"; +- reg = <0x00120000 0x006a0000>; +- }; +- partition@7c0000 { +- label = "VCTL"; +- reg = <0x007c0000 0x00010000>; +- read-only; +- }; +- partition@7d0000 { +- label = "cfg"; +- reg = <0x007d0000 0x00010000>; +- read-only; +- }; +- partition@7e0000 { +- label = "FIS"; +- reg = <0x007e0000 0x00010000>; +- read-only; ++ partitions { ++ compatible = "redboot-fis"; ++ /* Eraseblock at 0x7e0000 */ ++ fis-index-block = <0x3f>; + }; + }; + diff --git a/target/linux/gemini/patches-6.1/0027-ARM-dts-gemini-wbd222-Use-RedBoot-partion-parser.patch b/target/linux/gemini/patches-6.1/0027-ARM-dts-gemini-wbd222-Use-RedBoot-partion-parser.patch new file mode 100644 index 0000000000..8cafeaa0df --- /dev/null +++ b/target/linux/gemini/patches-6.1/0027-ARM-dts-gemini-wbd222-Use-RedBoot-partion-parser.patch @@ -0,0 +1,54 @@ +From 8558e2e1110a5daa4ac9e1c5b5c15e1651a8fb94 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Sun, 4 Dec 2022 20:02:30 +0100 +Subject: [PATCH 27/29] ARM: dts: gemini: wbd222: Use RedBoot partion parser + +This is clearly a RedBoot partitioned device with 0x20000 +sized erase blocks. + +Link: https://lore.kernel.org/r/20221204190230.3345590-3-linus.walleij@linaro.org +Signed-off-by: Linus Walleij +--- +--- a/arch/arm/boot/dts/gemini-wbd222.dts ++++ b/arch/arm/boot/dts/gemini-wbd222.dts +@@ -90,36 +90,11 @@ + status = "okay"; + /* 8MB of flash */ + reg = <0x30000000 0x00800000>; +- #address-cells = <1>; +- #size-cells = <1>; + +- partition@0 { +- label = "RedBoot"; +- reg = <0x00000000 0x00020000>; +- read-only; +- }; +- partition@20000 { +- label = "kernel"; +- reg = <0x00020000 0x00100000>; +- }; +- partition@120000 { +- label = "rootfs"; +- reg = <0x00120000 0x006a0000>; +- }; +- partition@7c0000 { +- label = "VCTL"; +- reg = <0x007c0000 0x00010000>; +- read-only; +- }; +- partition@7d0000 { +- label = "cfg"; +- reg = <0x007d0000 0x00010000>; +- read-only; +- }; +- partition@7e0000 { +- label = "FIS"; +- reg = <0x007e0000 0x00010000>; +- read-only; ++ partitions { ++ compatible = "redboot-fis"; ++ /* Eraseblock at 0x7e0000 */ ++ fis-index-block = <0x3f>; + }; + }; + diff --git a/target/linux/gemini/patches-6.1/0028-ARM-dts-gemini-Fix-USB-block-version.patch b/target/linux/gemini/patches-6.1/0028-ARM-dts-gemini-Fix-USB-block-version.patch new file mode 100644 index 0000000000..fb93b70a31 --- /dev/null +++ b/target/linux/gemini/patches-6.1/0028-ARM-dts-gemini-Fix-USB-block-version.patch @@ -0,0 +1,31 @@ +From d5c01ce4a1016507c69682894cf6b66301abca3d Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 23 Jan 2023 08:39:15 +0100 +Subject: [PATCH 28/29] ARM: dts: gemini: Fix USB block version + +The FOTG version in the Gemini is the FOTG200, fix this +up. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230123073916.2350839-1-linus.walleij@linaro.org +--- +--- a/arch/arm/boot/dts/gemini.dtsi ++++ b/arch/arm/boot/dts/gemini.dtsi +@@ -439,7 +439,7 @@ + }; + + usb0: usb@68000000 { +- compatible = "cortina,gemini-usb", "faraday,fotg210"; ++ compatible = "cortina,gemini-usb", "faraday,fotg200"; + reg = <0x68000000 0x1000>; + interrupts = <10 IRQ_TYPE_LEVEL_HIGH>; + resets = <&syscon GEMINI_RESET_USB0>; +@@ -460,7 +460,7 @@ + }; + + usb1: usb@69000000 { +- compatible = "cortina,gemini-usb", "faraday,fotg210"; ++ compatible = "cortina,gemini-usb", "faraday,fotg200"; + reg = <0x69000000 0x1000>; + interrupts = <11 IRQ_TYPE_LEVEL_HIGH>; + resets = <&syscon GEMINI_RESET_USB1>; diff --git a/target/linux/gemini/patches-6.1/0029-ARM-dts-gemini-Enable-DNS313-FOTG210-as-periph.patch b/target/linux/gemini/patches-6.1/0029-ARM-dts-gemini-Enable-DNS313-FOTG210-as-periph.patch new file mode 100644 index 0000000000..667878170b --- /dev/null +++ b/target/linux/gemini/patches-6.1/0029-ARM-dts-gemini-Enable-DNS313-FOTG210-as-periph.patch @@ -0,0 +1,54 @@ +From 296184694ae7a4e388603c95499e98d30b21cc09 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 23 Jan 2023 08:39:16 +0100 +Subject: [PATCH 29/29] ARM: dts: gemini: Enable DNS313 FOTG210 as periph + +Add the GPIO-based VBUS phy, and enable the FOTG210 +USB1 block for use as peripheral. + +Signed-off-by: Linus Walleij +Link: https://lore.kernel.org/r/20230123073916.2350839-2-linus.walleij@linaro.org +--- +--- a/arch/arm/boot/dts/gemini-dlink-dns-313.dts ++++ b/arch/arm/boot/dts/gemini-dlink-dns-313.dts +@@ -80,6 +80,15 @@ + #cooling-cells = <2>; + }; + ++ /* ++ * This is the type B USB connector on the device, ++ * a GPIO-controlled USB VBUS detect ++ */ ++ usb1_phy: phy { ++ compatible = "gpio-usb-b-connector", "usb-b-connector"; ++ #phy-cells = <0>; ++ vbus-gpios = <&gpio0 18 GPIO_ACTIVE_LOW>; ++ }; + + /* Global Mixed-Mode Technology G751 mounted on GPIO I2C */ + i2c { +@@ -302,5 +311,13 @@ + ide@63000000 { + status = "okay"; + }; ++ ++ usb@69000000 { ++ status = "okay"; ++ dr_mode = "peripheral"; ++ usb-phy = <&usb1_phy>; ++ pinctrl-names = "default"; ++ pinctrl-0 = <&usb_default_pins>; ++ }; + }; + }; +--- a/arch/arm/boot/dts/gemini.dtsi ++++ b/arch/arm/boot/dts/gemini.dtsi +@@ -455,6 +455,8 @@ + */ + pinctrl-names = "default"; + pinctrl-0 = <&usb_default_pins>; ++ /* Default to host mode */ ++ dr_mode = "host"; + syscon = <&syscon>; + status = "disabled"; + }; diff --git a/target/linux/gemini/patches-6.1/300-ARM-dts-Augment-DIR-685-partition-table-for-OpenWrt.patch b/target/linux/gemini/patches-6.1/300-ARM-dts-Augment-DIR-685-partition-table-for-OpenWrt.patch new file mode 100644 index 0000000000..99e0d2731d --- /dev/null +++ b/target/linux/gemini/patches-6.1/300-ARM-dts-Augment-DIR-685-partition-table-for-OpenWrt.patch @@ -0,0 +1,34 @@ +From 36ee838bf83c01cff7cb47c7b07be278d2950ac0 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Mon, 11 Mar 2019 15:44:29 +0100 +Subject: [PATCH 2/2] ARM: dts: Augment DIR-685 partition table for OpenWrt + +Rename the firmware partition so that the firmware MTD +splitter will do its job, drop the rootfs arguments as +the MTD splitter will set this up automatically. + +Signed-off-by: Linus Walleij +--- +--- a/arch/arm/boot/dts/gemini-dlink-dir-685.dts ++++ b/arch/arm/boot/dts/gemini-dlink-dir-685.dts +@@ -20,7 +20,7 @@ + }; + + chosen { +- bootargs = "console=ttyS0,19200n8 root=/dev/sda1 rw rootwait consoleblank=300"; ++ bootargs = "console=ttyS0,19200n8 consoleblank=300"; + stdout-path = "uart0:19200n8"; + }; + +@@ -317,9 +317,9 @@ + * this is called "upgrade" on the vendor system. + */ + partition@40000 { +- label = "upgrade"; ++ compatible = "wrg"; ++ label = "firmware"; + reg = <0x00040000 0x01f40000>; +- read-only; + }; + /* RGDB, Residental Gateway Database? */ + partition@1f80000 { From 0fe2aae0e1d638b875c80d54d193f5455cc744f6 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 31 May 2023 23:21:06 +0200 Subject: [PATCH 07/37] gemini: Create a config for kernel v6.1 This creates a config for the v6.1 kernel for Gemini. Signed-off-by: Linus Walleij --- target/linux/gemini/config-6.1 | 427 +++++++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 target/linux/gemini/config-6.1 diff --git a/target/linux/gemini/config-6.1 b/target/linux/gemini/config-6.1 new file mode 100644 index 0000000000..41f7093c1c --- /dev/null +++ b/target/linux/gemini/config-6.1 @@ -0,0 +1,427 @@ +CONFIG_ALIGNMENT_TRAP=y +CONFIG_AMBA_PL08X=y +CONFIG_ARCH_32BIT_OFF_T=y +CONFIG_ARCH_FORCE_MAX_ORDER=11 +CONFIG_ARCH_GEMINI=y +CONFIG_ARCH_KEEP_MEMBLOCK=y +CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y +# CONFIG_ARCH_MOXART is not set +CONFIG_ARCH_MULTIPLATFORM=y +CONFIG_ARCH_MULTI_V4=y +# CONFIG_ARCH_MULTI_V4T is not set +CONFIG_ARCH_MULTI_V4_V5=y +# CONFIG_ARCH_MULTI_V5 is not set +CONFIG_ARCH_NR_GPIO=0 +CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y +CONFIG_ARCH_SELECT_MEMORY_MODEL=y +CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARM=y +CONFIG_ARM_AMBA=y +CONFIG_ARM_APPENDED_DTB=y +# CONFIG_ARM_ATAG_DTB_COMPAT is not set +CONFIG_ARM_CRYPTO=y +CONFIG_ARM_HAS_SG_CHAIN=y +CONFIG_ARM_L1_CACHE_SHIFT=5 +CONFIG_ARM_PATCH_PHYS_VIRT=y +# CONFIG_ARM_SMMU is not set +CONFIG_ARM_UNWIND=y +CONFIG_ATA=y +CONFIG_ATAGS=y +CONFIG_ATA_FORCE=y +CONFIG_ATA_VERBOSE_ERROR=y +CONFIG_AUTO_ZRELADDR=y +CONFIG_BINFMT_FLAT_ARGVP_ENVP_ON_STACK=y +CONFIG_BLK_DEV_SD=y +CONFIG_BLK_MQ_PCI=y +CONFIG_BLK_PM=y +CONFIG_BOUNCE=y +CONFIG_CLKSRC_MMIO=y +CONFIG_CLONE_BACKWARDS=y +CONFIG_CMA=y +CONFIG_CMA_ALIGNMENT=8 +CONFIG_CMA_AREAS=7 +# CONFIG_CMA_DEBUG is not set +# CONFIG_CMA_DEBUGFS is not set +CONFIG_CMA_SIZE_PERCENTAGE=10 +# CONFIG_CMA_SIZE_SEL_MAX is not set +# CONFIG_CMA_SIZE_SEL_MBYTES is not set +# CONFIG_CMA_SIZE_SEL_MIN is not set +CONFIG_CMA_SIZE_SEL_PERCENTAGE=y +# CONFIG_CMA_SYSFS is not set +CONFIG_COMMON_CLK=y +CONFIG_COMMON_CLK_GEMINI=y +CONFIG_COMPAT_32BIT_TIME=y +CONFIG_CONSOLE_TRANSLATIONS=y +CONFIG_CONTIG_ALLOC=y +CONFIG_COREDUMP=y +CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS=y +CONFIG_CPU_32v4=y +CONFIG_CPU_ABRT_EV4=y +CONFIG_CPU_CACHE_FA=y +CONFIG_CPU_CACHE_VIVT=y +CONFIG_CPU_COPY_FA=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y +# CONFIG_CPU_DCACHE_WRITETHROUGH is not set +CONFIG_CPU_FA526=y +CONFIG_CPU_NO_EFFICIENT_FFS=y +CONFIG_CPU_PABRT_LEGACY=y +CONFIG_CPU_THERMAL=y +CONFIG_CPU_TLB_FA=y +CONFIG_CPU_USE_DOMAINS=y +CONFIG_CRASH_CORE=y +CONFIG_CRC16=y +# CONFIG_CRC32_SARWATE is not set +CONFIG_CRC32_SLICEBY8=y +CONFIG_CRC_CCITT=y +CONFIG_CRC_ITU_T=y +CONFIG_CROSS_MEMORY_ATTACH=y +CONFIG_CRYPTO_CMAC=y +CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_DES=y +CONFIG_CRYPTO_DEV_SL3516=y +# CONFIG_CRYPTO_DEV_SL3516_DEBUG is not set +CONFIG_CRYPTO_DRBG=y +CONFIG_CRYPTO_DRBG_HMAC=y +CONFIG_CRYPTO_DRBG_MENU=y +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_ECHAINIV=y +CONFIG_CRYPTO_ENGINE=y +CONFIG_CRYPTO_HMAC=y +CONFIG_CRYPTO_HW=y +CONFIG_CRYPTO_JITTERENTROPY=y +CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y +CONFIG_CRYPTO_LIB_DES=y +CONFIG_CRYPTO_LIB_SHA256=y +CONFIG_CRYPTO_MD4=y +CONFIG_CRYPTO_MD5=y +CONFIG_CRYPTO_RNG=y +CONFIG_CRYPTO_RNG2=y +CONFIG_CRYPTO_RNG_DEFAULT=y +CONFIG_CRYPTO_SEQIV=y +CONFIG_CRYPTO_SHA256=y +CONFIG_CRYPTO_SHA512=y +CONFIG_DEBUG_BUGVERBOSE=y +CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" +CONFIG_DEBUG_MEMORY_INIT=y +CONFIG_DECOMPRESS_BZIP2=y +CONFIG_DECOMPRESS_GZIP=y +CONFIG_DECOMPRESS_LZ4=y +CONFIG_DECOMPRESS_LZMA=y +CONFIG_DECOMPRESS_LZO=y +CONFIG_DECOMPRESS_XZ=y +CONFIG_DMADEVICES=y +CONFIG_DMATEST=y +CONFIG_DMA_CMA=y +CONFIG_DMA_ENGINE=y +CONFIG_DMA_ENGINE_RAID=y +CONFIG_DMA_OF=y +CONFIG_DMA_OPS=y +CONFIG_DMA_REMAP=y +CONFIG_DMA_SHARED_BUFFER=y +CONFIG_DMA_VIRTUAL_CHANNELS=y +CONFIG_DRM=y +CONFIG_DRM_BRIDGE=y +CONFIG_DRM_FBDEV_EMULATION=y +CONFIG_DRM_FBDEV_OVERALLOC=100 +CONFIG_DRM_GEM_CMA_HELPER=y +CONFIG_DRM_KMS_CMA_HELPER=y +CONFIG_DRM_KMS_HELPER=y +CONFIG_DRM_PANEL=y +CONFIG_DRM_PANEL_BRIDGE=y +CONFIG_DRM_PANEL_ILITEK_IL9322=y +CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y +CONFIG_DRM_TVE200=y +CONFIG_DTC=y +CONFIG_DUMMY_CONSOLE=y +CONFIG_EDAC_ATOMIC_SCRUB=y +CONFIG_EDAC_SUPPORT=y +CONFIG_EEPROM_93CX6=y +CONFIG_ELF_CORE=y +# CONFIG_EMBEDDED is not set +# CONFIG_EXPERT is not set +CONFIG_EXT4_FS=y +CONFIG_FARADAY_FTINTC010=y +CONFIG_FB=y +CONFIG_FB_CFB_COPYAREA=y +CONFIG_FB_CFB_FILLRECT=y +CONFIG_FB_CFB_IMAGEBLIT=y +CONFIG_FB_CMDLINE=y +CONFIG_FB_DEFERRED_IO=y +CONFIG_FB_SYS_COPYAREA=y +CONFIG_FB_SYS_FILLRECT=y +CONFIG_FB_SYS_FOPS=y +CONFIG_FB_SYS_IMAGEBLIT=y +CONFIG_FHANDLE=y +CONFIG_FIXED_PHY=y +CONFIG_FIX_EARLYCON_MEM=y +CONFIG_FONT_8x16=y +CONFIG_FONT_8x8=y +CONFIG_FONT_SUPPORT=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set +CONFIG_FS_IOMAP=y +CONFIG_FS_MBCACHE=y +CONFIG_FS_POSIX_ACL=y +CONFIG_FTTMR010_TIMER=y +CONFIG_FTWDT010_WATCHDOG=y +CONFIG_FWNODE_MDIO=y +CONFIG_FW_LOADER_PAGED_BUF=y +# CONFIG_FW_LOADER_USER_HELPER_FALLBACK is not set +CONFIG_GEMINI_ETHERNET=y +CONFIG_GENERIC_ALLOCATOR=y +CONFIG_GENERIC_ATOMIC64=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CPU_AUTOPROBE=y +CONFIG_GENERIC_EARLY_IOREMAP=y +CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_GENERIC_IRQ_MULTI_HANDLER=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_GENERIC_PINCONF=y +CONFIG_GENERIC_SCHED_CLOCK=y +CONFIG_GENERIC_SMP_IDLE_THREAD=y +CONFIG_GENERIC_STRNCPY_FROM_USER=y +CONFIG_GENERIC_STRNLEN_USER=y +CONFIG_GLOB=y +CONFIG_GPIOLIB_IRQCHIP=y +CONFIG_GPIO_CDEV=y +CONFIG_GPIO_FTGPIO010=y +CONFIG_GPIO_GENERIC=y +CONFIG_GRO_CELLS=y +CONFIG_HANDLE_DOMAIN_IRQ=y +CONFIG_HARDIRQS_SW_RESEND=y +CONFIG_HAS_DMA=y +CONFIG_HAS_IOMEM=y +CONFIG_HAS_IOPORT_MAP=y +CONFIG_HDMI=y +CONFIG_HIGHMEM=y +CONFIG_HIGHPTE=y +CONFIG_HWMON=y +CONFIG_HW_CONSOLE=y +CONFIG_HW_RANDOM=y +CONFIG_HZ_FIXED=0 +CONFIG_I2C=y +CONFIG_I2C_ALGOBIT=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_GPIO=y +CONFIG_I2C_HELPER_AUTO=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_INPUT=y +CONFIG_INPUT_KEYBOARD=y +# CONFIG_IOMMU_DEBUGFS is not set +# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set +# CONFIG_IOMMU_IO_PGTABLE_LPAE is not set +CONFIG_IOMMU_SUPPORT=y +CONFIG_IPC_NS=y +CONFIG_IRQCHIP=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_IRQ_WORK=y +# CONFIG_ISDN is not set +CONFIG_JBD2=y +CONFIG_KALLSYMS=y +CONFIG_KCMP=y +CONFIG_KERNEL_LZMA=y +# CONFIG_KERNEL_XZ is not set +CONFIG_KEXEC=y +CONFIG_KEXEC_CORE=y +CONFIG_KEYBOARD_DLINK_DIR685=y +CONFIG_KMAP_LOCAL=y +CONFIG_KMAP_LOCAL_NON_LINEAR_PTE_ARRAY=y +# CONFIG_LDM_DEBUG is not set +CONFIG_LDM_PARTITION=y +CONFIG_LEDS_TRIGGER_DISK=y +CONFIG_LIBFDT=y +CONFIG_LOCK_DEBUGGING_SUPPORT=y +CONFIG_LOGO=y +CONFIG_LOGO_LINUX_CLUT224=y +# CONFIG_LOGO_LINUX_MONO is not set +CONFIG_LOGO_LINUX_VGA16=y +CONFIG_LZ4_DECOMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_MARVELL_PHY=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_BUS=y +CONFIG_MDIO_DEVICE=y +CONFIG_MDIO_DEVRES=y +CONFIG_MDIO_GPIO=y +CONFIG_MEMFD_CREATE=y +CONFIG_MEMORY_ISOLATION=y +CONFIG_MFD_SYSCON=y +CONFIG_MIGRATION=y +CONFIG_MODULES_USE_ELF_REL=y +# CONFIG_MODULE_UNLOAD is not set +CONFIG_MQ_IOSCHED_DEADLINE=y +CONFIG_MQ_IOSCHED_KYBER=y +CONFIG_MTD_CFI_STAA=y +CONFIG_MTD_JEDECPROBE=y +CONFIG_MTD_PHYSMAP=y +CONFIG_MTD_PHYSMAP_GEMINI=y +CONFIG_MTD_REDBOOT_PARTS=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_WRGG_FW=y +CONFIG_NAMESPACES=y +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_NEED_KUSER_HELPERS=y +CONFIG_NEED_PER_CPU_KM=y +CONFIG_NET_DEVLINK=y +CONFIG_NET_DSA=y +CONFIG_NET_DSA_REALTEK_SMI=y +CONFIG_NET_DSA_TAG_RTL4_A=y +CONFIG_NET_NS=y +CONFIG_NET_SELFTESTS=y +CONFIG_NET_SWITCHDEV=y +CONFIG_NLS=y +CONFIG_NO_HZ_COMMON=y +CONFIG_NO_HZ_IDLE=y +CONFIG_NVMEM=y +CONFIG_OF=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_FLATTREE=y +CONFIG_OF_GPIO=y +CONFIG_OF_IRQ=y +CONFIG_OF_KOBJ=y +CONFIG_OF_MDIO=y +CONFIG_OLD_SIGACTION=y +CONFIG_OLD_SIGSUSPEND3=y +CONFIG_PAGE_OFFSET=0xC0000000 +# CONFIG_PANIC_ON_OOPS is not set +CONFIG_PANIC_ON_OOPS_VALUE=0 +CONFIG_PANIC_TIMEOUT=0 +CONFIG_PATA_FTIDE010=y +CONFIG_PCI=y +CONFIG_PCIEASPM=y +CONFIG_PCIEASPM_DEFAULT=y +# CONFIG_PCIEASPM_PERFORMANCE is not set +# CONFIG_PCIEASPM_POWERSAVE is not set +# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_DOMAINS_GENERIC=y +CONFIG_PCI_FTPCI100=y +CONFIG_PERF_USE_VMALLOC=y +CONFIG_PGTABLE_LEVELS=2 +CONFIG_PHYLIB=y +CONFIG_PHYLINK=y +CONFIG_PID_NS=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_GEMINI=y +# CONFIG_PINCTRL_SINGLE is not set +CONFIG_PM=y +CONFIG_PM_CLK=y +CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_GEMINI_POWEROFF=y +CONFIG_POWER_RESET_SYSCON=y +CONFIG_PREEMPT=y +CONFIG_PREEMPTION=y +CONFIG_PREEMPT_COUNT=y +# CONFIG_PREEMPT_NONE is not set +CONFIG_PREEMPT_RCU=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_PTP_1588_CLOCK_OPTIONAL=y +CONFIG_RATIONAL=y +CONFIG_RD_BZIP2=y +CONFIG_RD_GZIP=y +CONFIG_RD_LZ4=y +CONFIG_RD_LZMA=y +CONFIG_RD_LZO=y +CONFIG_RD_XZ=y +CONFIG_REALTEK_PHY=y +CONFIG_REGMAP=y +CONFIG_REGMAP_I2C=y +CONFIG_REGMAP_MMIO=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y +CONFIG_RELAY=y +CONFIG_RESET_CONTROLLER=y +CONFIG_RSEQ=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_FTRTC010=y +CONFIG_RTC_I2C_AND_SPI=y +CONFIG_RTC_MC146818_LIB=y +CONFIG_RTC_NVMEM=y +CONFIG_SATA_GEMINI=y +CONFIG_SATA_HOST=y +CONFIG_SATA_PMP=y +CONFIG_SCSI=y +CONFIG_SCSI_COMMON=y +# CONFIG_SCSI_LOWLEVEL is not set +# CONFIG_SCSI_PROC_FS is not set +CONFIG_SENSORS_DRIVETEMP=y +CONFIG_SENSORS_GPIO_FAN=y +CONFIG_SENSORS_LM75=y +CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y +CONFIG_SERIAL_8250_EXAR=y +CONFIG_SERIAL_8250_FSL=y +CONFIG_SERIAL_8250_NR_UARTS=1 +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_8250_RUNTIME_UARTS=1 +CONFIG_SERIAL_MCTRL_GPIO=y +CONFIG_SERIAL_OF_PLATFORM=y +CONFIG_SERIO=y +CONFIG_SERIO_LIBPS2=y +CONFIG_SERIO_SERPORT=y +CONFIG_SG_POOL=y +CONFIG_SLUB_DEBUG=y +CONFIG_SPARSE_IRQ=y +CONFIG_SPI=y +CONFIG_SPI_BITBANG=y +CONFIG_SPI_GPIO=y +CONFIG_SPI_MASTER=y +CONFIG_SPLIT_PTLOCK_CPUS=999999 +CONFIG_SRCU=y +# CONFIG_STRIP_ASM_SYMS is not set +CONFIG_SWPHY=y +CONFIG_SYNC_FILE=y +CONFIG_SYSFS_SYSCALL=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_THERMAL=y +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0 +CONFIG_THERMAL_GOV_STEP_WISE=y +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_OF=y +CONFIG_TICK_CPU_ACCOUNTING=y +CONFIG_TIMER_OF=y +CONFIG_TIMER_PROBE=y +CONFIG_TMPFS_POSIX_ACL=y +CONFIG_TREE_RCU=y +CONFIG_TREE_SRCU=y +CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" +CONFIG_UNINLINE_SPIN_UNLOCK=y +CONFIG_UNUSED_BOARD_FILES=n +CONFIG_UNWINDER_ARM=y +CONFIG_USB_FOTG210=m +CONFIG_USB_FOTG210_HCD=y +CONFIG_USB_FOTG210_UDC=y +CONFIG_USB_GADGET=y +CONFIG_USB_GPIO_VBUS=y +CONFIG_USB_SUPPORT=y +CONFIG_USER_NS=y +CONFIG_USE_OF=y +CONFIG_UTS_NS=y +CONFIG_VGA_ARB=y +CONFIG_VGA_ARB_MAX_GPUS=16 +CONFIG_VITESSE_PHY=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +CONFIG_VT_HW_CONSOLE_BINDING=y +CONFIG_WATCHDOG_CORE=y +# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set +CONFIG_XZ_DEC_ARM=y +CONFIG_XZ_DEC_ARMTHUMB=y +CONFIG_XZ_DEC_BCJ=y +CONFIG_XZ_DEC_IA64=y +CONFIG_XZ_DEC_POWERPC=y +CONFIG_XZ_DEC_SPARC=y +CONFIG_XZ_DEC_X86=y +CONFIG_ZBOOT_ROM_BSS=0 +CONFIG_ZBOOT_ROM_TEXT=0 +CONFIG_ZLIB_INFLATE=y From 997bf7084c935f01031f1f461039b590d1e5f241 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Wed, 31 May 2023 23:21:08 +0200 Subject: [PATCH 08/37] gemini: Bump to kernel v6.1 This bumps the Gemini kernel to use v6.1. While there is no reason to stay with v5.15, I personally use newer upstream kernels constantly and they are tested and work well. OpenWrt's 6.1 needs more time until it can be switched. Signed-off-by: Linus Walleij Signed-off-by: Christian Lamparter --- target/linux/gemini/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/target/linux/gemini/Makefile b/target/linux/gemini/Makefile index 4266db16cd..284d1247cb 100644 --- a/target/linux/gemini/Makefile +++ b/target/linux/gemini/Makefile @@ -12,6 +12,7 @@ CPU_TYPE:=fa526 SUBTARGETS:=generic KERNEL_PATCHVER:=5.15 +KERNEL_TESTING_PATCHVER:=6.1 define Target/Description Build firmware images for the StorLink/Cortina Gemini CS351x ARM FA526 CPU From 75a3760862bd4e3a48c38ccd960beedb8d97f1b9 Mon Sep 17 00:00:00 2001 From: Klaus Kudielka Date: Sat, 26 Feb 2022 09:37:26 +0100 Subject: [PATCH 09/37] mvebu: stop building omnia-medkit Since August 2022, users of very old Turris Omnias have been encouraged to update U-Boot before OpenWrt installation [1]. The omnia-medkit (only useful for installation with U-Boot 2015.10-rc2) is not needed anymore. [1] https://openwrt.org/toh/turris/turris_omnia#installation Signed-off-by: Klaus Kudielka --- target/linux/mvebu/image/Makefile | 11 ----------- target/linux/mvebu/image/cortexa9.mk | 6 ++---- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/target/linux/mvebu/image/Makefile b/target/linux/mvebu/image/Makefile index 57129d2dcb..9d6f207b3f 100644 --- a/target/linux/mvebu/image/Makefile +++ b/target/linux/mvebu/image/Makefile @@ -136,17 +136,6 @@ define Build/sdcard-img-ext4 83 $(CONFIG_TARGET_ROOTFS_PARTSIZE) $(IMAGE_ROOTFS) endef -define Build/omnia-medkit-initramfs - $(TAR) -c -T /dev/null -f $@ - rm -rf $(dir $(IMAGE_KERNEL))boot - mkdir -p $(dir $(IMAGE_KERNEL))boot/boot/ - cp $(KDIR)/zImage-initramfs $(dir $(IMAGE_KERNEL))boot/boot/zImage - cp $(KDIR)/image-$(DEVICE_DTS).dtb $(dir $(IMAGE_KERNEL))boot/boot/dtb - $(TAR) -rp --numeric-owner --owner=0 --group=0 --sort=name \ - $(if $(SOURCE_DATE_EPOCH),--mtime="@$(SOURCE_DATE_EPOCH)") \ - --file=$@ -C $(dir $(IMAGE_KERNEL))boot/ . -endef - define Build/uDPU-firmware (rm -fR $@-fw; mkdir -p $@-fw) $(CP) $(BIN_DIR)/$(DEVICE_IMG_PREFIX)-initramfs.itb $@-fw/recovery.itb diff --git a/target/linux/mvebu/image/cortexa9.mk b/target/linux/mvebu/image/cortexa9.mk index 248f1cd6fd..ba6213a89e 100644 --- a/target/linux/mvebu/image/cortexa9.mk +++ b/target/linux/mvebu/image/cortexa9.mk @@ -88,10 +88,8 @@ define Device/cznic_turris-omnia mkf2fs e2fsprogs kmod-fs-vfat kmod-nls-cp437 kmod-nls-iso8859-1 \ wpad-basic-mbedtls kmod-ath9k kmod-ath10k-ct ath10k-firmware-qca988x-ct \ partx-utils kmod-i2c-mux-pca954x kmod-leds-turris-omnia - IMAGES := $$(DEVICE_IMG_PREFIX)-sysupgrade.img.gz omnia-medkit-$$(DEVICE_IMG_PREFIX)-initramfs.tar.gz - IMAGE/$$(DEVICE_IMG_PREFIX)-sysupgrade.img.gz := boot-scr | boot-img | sdcard-img | gzip | append-metadata - IMAGE/omnia-medkit-$$(DEVICE_IMG_PREFIX)-initramfs.tar.gz := omnia-medkit-initramfs | gzip - DEVICE_IMG_NAME = $$(2) + IMAGES := sysupgrade.img.gz + IMAGE/sysupgrade.img.gz := boot-scr | boot-img | sdcard-img | gzip | append-metadata SUPPORTED_DEVICES += armada-385-turris-omnia BOOT_SCRIPT := turris-omnia endef From 9f7fdd001750a818c685cb30dfdc8020b0201784 Mon Sep 17 00:00:00 2001 From: Klaus Kudielka Date: Sun, 14 Aug 2022 14:17:19 +0200 Subject: [PATCH 10/37] mvebu: remove hack for Turris Omnia legacy U-Boot The omnia-medkit (only useful for installation with U-Boot 2015.10-rc2) is not being built anymore. Now we can be reasonably sure, that there won't be first-time OpenWrt boots with that U-Boot version, and can get rid of a rather ugly hack. Signed-off-by: Klaus Kudielka --- .../uci-defaults/35_turris-omnia_uboot-env | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 target/linux/mvebu/cortexa9/base-files/etc/uci-defaults/35_turris-omnia_uboot-env diff --git a/target/linux/mvebu/cortexa9/base-files/etc/uci-defaults/35_turris-omnia_uboot-env b/target/linux/mvebu/cortexa9/base-files/etc/uci-defaults/35_turris-omnia_uboot-env deleted file mode 100644 index da025a200f..0000000000 --- a/target/linux/mvebu/cortexa9/base-files/etc/uci-defaults/35_turris-omnia_uboot-env +++ /dev/null @@ -1,44 +0,0 @@ -# This must be sourced after 30_uboot-envtools - -. /lib/functions.sh - -board=$(board_name) - -case "$board" in -cznic,turris-omnia) - # Do nothing if this is not the old U-Boot - grep -q 'U-Boot 2015.10-rc2' /dev/mtd0 || exit 0 - # Do nothing if we already have distro_bootcmd - fw_printenv distro_bootcmd >/dev/null 2>/dev/null && exit 0 - # Set the complete environment, since U-Boot does not merge the default environment from its own image! - fw_setenv -s - <<-"EOF" - baudrate 115200 - bootdelay 3 - ethact neta2 - fdt_high 0x10000000 - initrd_high 0x10000000 - bootargs earlyprintk console=ttyS0,115200 rootfstype=btrfs rootdelay=2 root=b301 rootflags=subvol=@,commit=5 rw - bootcmd i2c dev 1; i2c read 0x2a 0x9 1 0x00FFFFF0; setexpr.b rescue *0x00FFFFF0; if test $rescue -ge 1; then echo BOOT RESCUE; run rescueboot; else echo BOOT eMMC FS; run mmcboot; setenv bootargs; run distro_bootcmd; fi - rescueboot i2c mw 0x2a.1 0x3 0x1c 1; i2c mw 0x2a.1 0x4 0x1c 1; mw.l 0x01000000 0x00ff000c; i2c write 0x01000000 0x2a.1 0x5 4 -s; setenv bootargs "$bootargs omniarescue=$rescue"; sf probe; sf read 0x1000000 0x100000 0x700000; bootz 0x1000000 - mmcboot btrload mmc 0 ${kernel_addr_r} boot/zImage @ && btrload mmc 0 ${fdt_addr_r} boot/dtb @ && setenv bootargs ${bootargs} cfg80211.freg=${regdomain} && bootz ${kernel_addr_r} - ${fdt_addr_r} - kernel_addr_r 0x1000000 - scriptaddr 0x1800000 - fdt_addr_r 0x2000000 - boot_targets mmc0 scsi0 - boot_prefixes / /boot/ - boot_scripts boot.scr - distro_bootcmd scsi_need_init=true; for target in ${boot_targets}; do run bootcmd_${target}; done - bootcmd_mmc0 devnum=0; run mmc_boot - mmc_boot if mmc dev ${devnum}; then devtype=mmc; run scan_dev_for_boot_part; fi - bootcmd_scsi0 devnum=0; run scsi_boot - scsi_boot run scsi_init; if scsi dev ${devnum}; then devtype=scsi; run scan_dev_for_boot_part; fi - scsi_init if ${scsi_need_init}; then scsi_need_init=false; scsi scan; fi - scan_dev_for_boot_part for distro_bootpart in 1; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done - scan_dev_for_boot echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_scripts; done - scan_dev_for_scripts for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done - boot_a_script load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr} - EOF - ;; -esac - -exit 0 From 6f607ba043cdc996cd113b8dc25b6965dc1d9a41 Mon Sep 17 00:00:00 2001 From: Tianling Shen Date: Thu, 1 Jun 2023 17:35:16 +0800 Subject: [PATCH 11/37] firmware-utils: add missing build dependencies Fixes the following build error: ``` CMake Error at CMakeLists.txt:9 (MESSAGE): Unable to find zlib library. CMake Error at CMakeLists.txt:13 (MESSAGE): Unable to find OpenSSL librry. ``` Fixes: 24d6abe2d7cd8b ("firmware-utils: new package replacing otrx") Signed-off-by: Tianling Shen --- package/utils/firmware-utils/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package/utils/firmware-utils/Makefile b/package/utils/firmware-utils/Makefile index f49cca01bb..1316fa5c6b 100644 --- a/package/utils/firmware-utils/Makefile +++ b/package/utils/firmware-utils/Makefile @@ -3,7 +3,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=firmware-utils -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL=$(PROJECT_GIT)/project/firmware-utils.git @@ -11,6 +11,8 @@ PKG_SOURCE_DATE:=2023-05-18 PKG_SOURCE_VERSION:=02cdbc6a4d61605c008efef09162f772f553fcde PKG_MIRROR_HASH:=f5188fc38bb03ddbcc34763ff049597e2d8af98c0854910dc87f10e5927096e2 +PKG_BUILD_DEPENDS:=openssl zlib + include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk From edb3a4162c0763ecc9d5e7660700a68a25bf28e3 Mon Sep 17 00:00:00 2001 From: Yanase Yuki Date: Wed, 31 May 2023 16:41:59 +0900 Subject: [PATCH 12/37] ipq40xx: convert Buffalo WTR-M2133HP to DSA This commit convert WTR-M2133HP to DSA setup. Signed-off-by: Yanase Yuki --- .../ipq40xx/base-files/etc/board.d/02_network | 1 + .../arm/boot/dts/qcom-ipq4019-wtr-m2133hp.dts | 29 +++++++++++++++++++ target/linux/ipq40xx/image/generic.mk | 3 +- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/target/linux/ipq40xx/base-files/etc/board.d/02_network b/target/linux/ipq40xx/base-files/etc/board.d/02_network index d6b19c981e..b131d0d618 100644 --- a/target/linux/ipq40xx/base-files/etc/board.d/02_network +++ b/target/linux/ipq40xx/base-files/etc/board.d/02_network @@ -82,6 +82,7 @@ ipq40xx_setup_interfaces() ucidef_set_interface_lan "sw-eth1 sw-eth2" ;; aruba,ap-303h|\ + buffalo,wtr-m2133hp|\ ezviz,cs-w3-wd1200g-eup|\ netgear,rbr50|\ netgear,rbs50|\ diff --git a/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-wtr-m2133hp.dts b/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-wtr-m2133hp.dts index f1d58ccba9..3260de23bd 100644 --- a/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-wtr-m2133hp.dts +++ b/target/linux/ipq40xx/files/arch/arm/boot/dts/qcom-ipq4019-wtr-m2133hp.dts @@ -417,6 +417,35 @@ qcom,ath10k-calibration-variant = "Buffalo-WTR-M2133HP"; }; +&switch { + status = "okay"; +}; + +&swport2 { + status = "okay"; + label = "lan3"; +}; + +&swport3 { + status = "okay"; + label = "lan2"; +}; + +&swport4 { + status = "okay"; + label = "lan1"; +}; + +&swport5 { + status = "okay"; +}; + +&gmac { + status = "okay"; + nvmem-cell-names = "mac-address"; + nvmem-cells = <&macaddr_orgdata_20>; +}; + &mdio { status = "okay"; pinctrl-0 = <&mdio_pins>; diff --git a/target/linux/ipq40xx/image/generic.mk b/target/linux/ipq40xx/image/generic.mk index 7c7a4acc5c..59d4f082ff 100644 --- a/target/linux/ipq40xx/image/generic.mk +++ b/target/linux/ipq40xx/image/generic.mk @@ -305,8 +305,7 @@ define Device/buffalo_wtr-m2133hp BLOCKSIZE := 128k PAGESIZE := 2048 endef -# Missing DSA Setup -#TARGET_DEVICES += buffalo_wtr-m2133hp +TARGET_DEVICES += buffalo_wtr-m2133hp define Device/cellc_rtl30vw KERNEL_SUFFIX := -zImage.itb From 16a20512d852f6ecebf8c57cd7fa2572a06a9d0b Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Tue, 30 May 2023 20:21:43 +0200 Subject: [PATCH 13/37] kernel: Set CONFIG_FRAME_WARN depending on target This set the CONFIG_FRAME_WARN option depending on some target settings. It will use the default from the upstream kernel and not the hard coded value of 1024 now. Signed-off-by: Hauke Mehrtens --- config/Config-kernel.in | 11 +++++++++++ target/linux/generic/config-5.15 | 1 - target/linux/generic/config-6.1 | 1 - target/linux/layerscape/armv8_64b/config-5.15 | 1 - target/linux/octeon/config-5.15 | 1 - target/linux/rockchip/armv8/config-5.15 | 1 - target/linux/rockchip/armv8/config-6.1 | 1 - target/linux/sifiveu/config-5.15 | 1 - target/linux/sunxi/config-5.15 | 1 - 9 files changed, 11 insertions(+), 8 deletions(-) diff --git a/config/Config-kernel.in b/config/Config-kernel.in index 6c5e6a967a..7de0d17b5e 100644 --- a/config/Config-kernel.in +++ b/config/Config-kernel.in @@ -397,6 +397,17 @@ config KERNEL_DEBUG_INFO_REDUCED DEBUG_INFO build and compile times are reduced too. Only works with newer gcc versions. +config KERNEL_FRAME_WARN + int + range 0 8192 + default 1280 if KERNEL_KASAN && !ARCH_64BIT + default 1024 if !ARCH_64BIT + default 2048 if ARCH_64BIT + help + Tell the compiler to warn at build time for stack frames larger than this. + Setting this too low will cause a lot of warnings. + Setting it to 0 disables the warning. + # KERNEL_DEBUG_LL symbols must have the default value set as otherwise # KConfig wont evaluate them unless KERNEL_EARLY_PRINTK is selected # which means that buildroot wont override the DEBUG_LL symbols in target diff --git a/target/linux/generic/config-5.15 b/target/linux/generic/config-5.15 index 0f2f156728..340175f00d 100644 --- a/target/linux/generic/config-5.15 +++ b/target/linux/generic/config-5.15 @@ -2077,7 +2077,6 @@ CONFIG_FORTIFY_SOURCE=y # CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER is not set # CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION is not set # CONFIG_FRAME_POINTER is not set -CONFIG_FRAME_WARN=1024 # CONFIG_FREEZER is not set # CONFIG_FRONTSWAP is not set # CONFIG_FSCACHE is not set diff --git a/target/linux/generic/config-6.1 b/target/linux/generic/config-6.1 index 74a2a3e3a7..18d4ec0bd8 100644 --- a/target/linux/generic/config-6.1 +++ b/target/linux/generic/config-6.1 @@ -2156,7 +2156,6 @@ CONFIG_FORTIFY_SOURCE=y # CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER is not set # CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION is not set # CONFIG_FRAME_POINTER is not set -CONFIG_FRAME_WARN=1024 # CONFIG_FREEZER is not set # CONFIG_FRONTSWAP is not set # CONFIG_FSCACHE is not set diff --git a/target/linux/layerscape/armv8_64b/config-5.15 b/target/linux/layerscape/armv8_64b/config-5.15 index b4510b7d34..69edc7a3f0 100644 --- a/target/linux/layerscape/armv8_64b/config-5.15 +++ b/target/linux/layerscape/armv8_64b/config-5.15 @@ -269,7 +269,6 @@ CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y # CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set CONFIG_FRAME_POINTER=y -CONFIG_FRAME_WARN=2048 CONFIG_FREEZER=y # CONFIG_FSL_BMAN_TEST is not set CONFIG_FSL_DPAA=y diff --git a/target/linux/octeon/config-5.15 b/target/linux/octeon/config-5.15 index 450b84be44..5882c7dd75 100644 --- a/target/linux/octeon/config-5.15 +++ b/target/linux/octeon/config-5.15 @@ -70,7 +70,6 @@ CONFIG_EXT4_FS=y CONFIG_F2FS_FS=y CONFIG_FAT_FS=y CONFIG_FIXED_PHY=y -CONFIG_FRAME_WARN=2048 CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y CONFIG_FWNODE_MDIO=y diff --git a/target/linux/rockchip/armv8/config-5.15 b/target/linux/rockchip/armv8/config-5.15 index 0ed009004f..08ce65d8ea 100644 --- a/target/linux/rockchip/armv8/config-5.15 +++ b/target/linux/rockchip/armv8/config-5.15 @@ -201,7 +201,6 @@ CONFIG_FIXED_PHY=y CONFIG_FIX_EARLYCON_MEM=y # CONFIG_FORTIFY_SOURCE is not set CONFIG_FRAME_POINTER=y -CONFIG_FRAME_WARN=2048 CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y CONFIG_FS_POSIX_ACL=y diff --git a/target/linux/rockchip/armv8/config-6.1 b/target/linux/rockchip/armv8/config-6.1 index 57f481ccd4..4d747537e2 100644 --- a/target/linux/rockchip/armv8/config-6.1 +++ b/target/linux/rockchip/armv8/config-6.1 @@ -227,7 +227,6 @@ CONFIG_FIXED_PHY=y CONFIG_FIX_EARLYCON_MEM=y # CONFIG_FORTIFY_SOURCE is not set CONFIG_FRAME_POINTER=y -CONFIG_FRAME_WARN=2048 CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y CONFIG_FS_POSIX_ACL=y diff --git a/target/linux/sifiveu/config-5.15 b/target/linux/sifiveu/config-5.15 index fce0f659e3..de3fe5b46f 100644 --- a/target/linux/sifiveu/config-5.15 +++ b/target/linux/sifiveu/config-5.15 @@ -93,7 +93,6 @@ CONFIG_FONT_AUTOSELECT=y CONFIG_FONT_SUPPORT=y CONFIG_FPU=y CONFIG_FRAME_POINTER=y -CONFIG_FRAME_WARN=2048 CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y CONFIG_FWNODE_MDIO=y diff --git a/target/linux/sunxi/config-5.15 b/target/linux/sunxi/config-5.15 index 9771ee8b4c..c33bb7ab6f 100644 --- a/target/linux/sunxi/config-5.15 +++ b/target/linux/sunxi/config-5.15 @@ -161,7 +161,6 @@ CONFIG_FONT_SUPPORT=y CONFIG_FRAMEBUFFER_CONSOLE=y CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y -CONFIG_FRAME_WARN=2048 CONFIG_FREEZER=y CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y From e0f06ddc23b2503a1791ae7e97b02e2647e8a70d Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 19 Jan 2022 02:25:23 +0000 Subject: [PATCH 14/37] armvirt: add EFI support EFI booting is used on newer machines compatible with the Arm SystemReady specifications. This commit restructures armvirt into a more 'generic' target similar to x86. See https://github.com/openwrt/openwrt/pull/4956 for a history of this port. Signed-off-by: Mathew McBride --- target/linux/armvirt/32/config-6.1 | 2 + target/linux/armvirt/32/target.mk | 2 +- target/linux/armvirt/64/target.mk | 4 +- target/linux/armvirt/Makefile | 5 +- .../armvirt/base-files/etc/board.d/01_led | 19 ++ .../armvirt/base-files/etc/board.d/02_network | 18 ++ .../base-files/etc/board.d/03_gpio_switches | 23 +++ .../base-files/lib/preinit/01_sysinfo_acpi | 52 ++++++ .../base-files/lib/upgrade/platform.sh | 164 ++++++++++++++++++ target/linux/armvirt/image/Makefile | 111 ++++++++++-- target/linux/armvirt/image/grub-efi.cfg | 14 ++ 11 files changed, 394 insertions(+), 20 deletions(-) create mode 100644 target/linux/armvirt/base-files/etc/board.d/01_led create mode 100644 target/linux/armvirt/base-files/etc/board.d/02_network create mode 100644 target/linux/armvirt/base-files/etc/board.d/03_gpio_switches create mode 100644 target/linux/armvirt/base-files/lib/preinit/01_sysinfo_acpi create mode 100644 target/linux/armvirt/base-files/lib/upgrade/platform.sh create mode 100644 target/linux/armvirt/image/grub-efi.cfg diff --git a/target/linux/armvirt/32/config-6.1 b/target/linux/armvirt/32/config-6.1 index 931607aade..664ef2e05b 100644 --- a/target/linux/armvirt/32/config-6.1 +++ b/target/linux/armvirt/32/config-6.1 @@ -4,6 +4,7 @@ CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y CONFIG_ARCH_MULTIPLATFORM=y CONFIG_ARCH_MULTI_V6_V7=y CONFIG_ARCH_MULTI_V7=y +CONFIG_ARCH_MMAP_RND_BITS=8 CONFIG_ARCH_NR_GPIO=0 CONFIG_ARCH_OPTIONAL_KERNEL_RWX=y CONFIG_ARCH_OPTIONAL_KERNEL_RWX_DEFAULT=y @@ -13,6 +14,7 @@ CONFIG_ARM=y CONFIG_ARM_CPU_SUSPEND=y CONFIG_ARM_HAS_SG_CHAIN=y CONFIG_ARM_HEAVY_MB=y +# CONFIG_ARM_HIGHBANK_CPUIDLE is not set CONFIG_ARM_L1_CACHE_SHIFT=6 CONFIG_ARM_L1_CACHE_SHIFT_6=y CONFIG_ARM_LPAE=y diff --git a/target/linux/armvirt/32/target.mk b/target/linux/armvirt/32/target.mk index 8d42a14b7c..df22040241 100644 --- a/target/linux/armvirt/32/target.mk +++ b/target/linux/armvirt/32/target.mk @@ -1,6 +1,6 @@ ARCH:=arm SUBTARGET:=32 -BOARDNAME:=QEMU ARM Virtual Machine (cortex-a15) +BOARDNAME:=32-bit ARM QEMU Virtual Machine CPU_TYPE:=cortex-a15 CPU_SUBTYPE:=neon-vfpv4 KERNELNAME:=zImage diff --git a/target/linux/armvirt/64/target.mk b/target/linux/armvirt/64/target.mk index 58adcc7d60..ac5a60d848 100644 --- a/target/linux/armvirt/64/target.mk +++ b/target/linux/armvirt/64/target.mk @@ -1,8 +1,6 @@ ARCH:=aarch64 SUBTARGET:=64 -BOARDNAME:=QEMU ARMv8 Virtual Machine (cortex-a53) -CPU_TYPE:=cortex-a53 -KERNELNAME:=Image +BOARDNAME:=64-bit ARM machines define Target/Description Build multi-platform images for the ARMv8 instruction set architecture diff --git a/target/linux/armvirt/Makefile b/target/linux/armvirt/Makefile index 3acf84e5c9..36c91a077f 100644 --- a/target/linux/armvirt/Makefile +++ b/target/linux/armvirt/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk BOARD:=armvirt BOARDNAME:=QEMU ARM Virtual Machine -FEATURES:=fpu pci rtc usb +FEATURES:=fpu pci pcie rtc usb boot-part rootfs-part FEATURES+=cpiogz ext4 ramdisk squashfs targz KERNEL_PATCHVER:=5.15 @@ -15,5 +15,8 @@ KERNEL_TESTING_PATCHVER:=6.1 include $(INCLUDE_DIR)/target.mk DEFAULT_PACKAGES += mkf2fs e2fsprogs +# blkid used for resolving PARTUUID +# in sysupgrade +DEFAULT_PACKAGES += blkid $(eval $(call BuildTarget)) diff --git a/target/linux/armvirt/base-files/etc/board.d/01_led b/target/linux/armvirt/base-files/etc/board.d/01_led new file mode 100644 index 0000000000..0250a9672f --- /dev/null +++ b/target/linux/armvirt/base-files/etc/board.d/01_led @@ -0,0 +1,19 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +. /lib/functions/uci-defaults.sh + +board_config_update + +board=$(board_name) + +case "$board" in +traverse,ten64) + ucidef_set_led_netdev "sfp1" "SFP 1" "ten64:green:sfp1:down" "eth8" "link tx rx" + ucidef_set_led_netdev "sfp2" "SFP 2" "ten64:green:sfp2:up" "eth9" "link tx rx" + ;; +esac + +board_config_flush + +exit 0 diff --git a/target/linux/armvirt/base-files/etc/board.d/02_network b/target/linux/armvirt/base-files/etc/board.d/02_network new file mode 100644 index 0000000000..f58de1c27d --- /dev/null +++ b/target/linux/armvirt/base-files/etc/board.d/02_network @@ -0,0 +1,18 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +. /lib/functions/system.sh +. /lib/functions/uci-defaults.sh + +board_config_update + +case "$(board_name)" in + traverse,ten64) + ucidef_set_interface_lan "eth0 eth1 eth2 eth3" + ucidef_set_interface_wan "eth6" + ;; +esac + +board_config_flush + +exit 0 diff --git a/target/linux/armvirt/base-files/etc/board.d/03_gpio_switches b/target/linux/armvirt/base-files/etc/board.d/03_gpio_switches new file mode 100644 index 0000000000..cf07bc0f54 --- /dev/null +++ b/target/linux/armvirt/base-files/etc/board.d/03_gpio_switches @@ -0,0 +1,23 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later + +. /lib/functions/uci-defaults.sh + +board_config_update + +board=$(board_name) + +case "$board" in +traverse,ten64) + ucidef_add_gpio_switch "lte_reset" "Cell Modem Reset" "376" + ucidef_add_gpio_switch "lte_power" "Cell Modem Power" "377" + ucidef_add_gpio_switch "lte_disable" "Cell Modem Airplane mode" "378" + ucidef_add_gpio_switch "gnss_disable" "Cell Modem Disable GNSS receiver" "379" + ucidef_add_gpio_switch "lower_sfp_txidsable" "Lower SFP+ TX Disable" "369" + ucidef_add_gpio_switch "upper_sfp_txdisable" "Upper SFP+ TX Disable" "373" + ;; +esac + +board_config_flush + +exit 0 diff --git a/target/linux/armvirt/base-files/lib/preinit/01_sysinfo_acpi b/target/linux/armvirt/base-files/lib/preinit/01_sysinfo_acpi new file mode 100644 index 0000000000..1069d74cf3 --- /dev/null +++ b/target/linux/armvirt/base-files/lib/preinit/01_sysinfo_acpi @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +sanitize_name_arm64() { + sed -e ' + y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/; + s/[^a-z0-9_-]\+/-/g; + s/^-//; + s/-$//; + ' "$@" +} + +do_sysinfo_arm64() { + local vendor product file + + for file in sys_vendor board_vendor; do + vendor="$(cat /sys/devices/virtual/dmi/id/$file 2>/dev/null)" + case "$vendor" in + empty | \ + System\ manufacturer | \ + To\ [bB]e\ [fF]illed\ [bB]y\ O\.E\.M\.) + continue + ;; + esac + [ -n "$vendor" ] && break + done + + for file in product_name board_name; do + product="$(cat /sys/devices/virtual/dmi/id/$file 2>/dev/null)" + case "$vendor:$product" in + ?*:empty | \ + ?*:System\ Product\ Name | \ + ?*:To\ [bB]e\ [fF]illed\ [bB]y\ O\.E\.M\.) + continue + ;; + ?*:?*) + break + ;; + esac + done + + [ -d "/sys/firmware/devicetree/base" ] && return + + [ -n "$vendor" -a -n "$product" ] || return + + mkdir -p /tmp/sysinfo + + echo "$vendor $product" > /tmp/sysinfo/model + + sanitize_name_arm64 /tmp/sysinfo/model > /tmp/sysinfo/board_name +} + +boot_hook_add preinit_main do_sysinfo_arm64 diff --git a/target/linux/armvirt/base-files/lib/upgrade/platform.sh b/target/linux/armvirt/base-files/lib/upgrade/platform.sh new file mode 100644 index 0000000000..8263b9c7e3 --- /dev/null +++ b/target/linux/armvirt/base-files/lib/upgrade/platform.sh @@ -0,0 +1,164 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +RAMFS_COPY_BIN="/usr/sbin/blkid" + +platform_check_image() { + local board=$(board_name) + local diskdev partdev diff + [ "$#" -gt 1 ] && return 1 + + v "Board is ${board}" + + export_bootdevice && export_partdevice diskdev 0 || { + v "platform_check_image: Unable to determine upgrade device" + return 1 + } + + get_partitions "/dev/$diskdev" bootdisk + + v "Extract boot sector from the image" + get_image_dd "$1" of=/tmp/image.bs count=63 bs=512b + + get_partitions /tmp/image.bs image + + #compare tables + diff="$(grep -F -x -v -f /tmp/partmap.bootdisk /tmp/partmap.image)" + + rm -f /tmp/image.bs /tmp/partmap.bootdisk /tmp/partmap.image + + if [ -n "$diff" ]; then + v "Partition layout has changed. Full image will be written." + ask_bool 0 "Abort" && exit 1 + return 0 + fi +} + +platform_copy_config() { + local partdev parttype=ext4 + + if export_partdevice partdev 2; then + mount -t $parttype -o rw,noatime "/dev/$partdev" /mnt + cp -af "$UPGRADE_BACKUP" "/mnt/$BACKUP_FILE" + umount /mnt + else + v "ERROR: Unable to find partition to copy config data to" + fi + + sleep 5 +} + +# To avoid writing over any firmware +# files (e.g ubootefi.var or firmware/X/ aka EBBR) +# Copy efi/openwrt and efi/boot from the new image +# to the existing ESP +platform_do_upgrade_efi_system_partition() { + local image_file=$1 + local target_partdev=$2 + local image_efisp_start=$3 + local image_efisp_size=$4 + + v "Updating ESP on ${target_partdev}" + NEW_ESP_DIR="/mnt/new_esp_loop" + CUR_ESP_DIR="/mnt/cur_esp" + mkdir "${NEW_ESP_DIR}" + mkdir "${CUR_ESP_DIR}" + + get_image_dd "$image_file" of="/tmp/new_efi_sys_part.img" \ + skip="$image_efisp_start" count="$image_efisp_size" + + mount -t vfat -o loop -o ro /tmp/new_efi_sys_part.img "${NEW_ESP_DIR}" + if [ ! -d "${NEW_ESP_DIR}/efi/boot" ]; then + v "ERROR: Image does not contain EFI boot files (/efi/boot)" + return 1 + fi + + mount -t vfat "/dev/$partdev" "${CUR_ESP_DIR}" + + for d in $(find "${NEW_ESP_DIR}/efi/" -mindepth 1 -maxdepth 1 -type d); do + v "Copying ${d}" + newdir_bname=$(basename "${d}") + rm -rf "${CUR_ESP_DIR}/efi/${newdir_bname}" + cp -r "${d}" "${CUR_ESP_DIR}/efi" + done + + umount "${NEW_ESP_DIR}" + umount "${CUR_ESP_DIR}" +} + +platform_do_upgrade() { + local board=$(board_name) + local diskdev partdev diff + + export_bootdevice && export_partdevice diskdev 0 || { + v "platform_do_upgrade: Unable to determine upgrade device" + return 1 + } + + sync + + if [ "$UPGRADE_OPT_SAVE_PARTITIONS" = "1" ]; then + get_partitions "/dev/$diskdev" bootdisk + + v "Extract boot sector from the image" + get_image_dd "$1" of=/tmp/image.bs count=63 bs=512b + + get_partitions /tmp/image.bs image + + #compare tables + diff="$(grep -F -x -v -f /tmp/partmap.bootdisk /tmp/partmap.image)" + else + diff=1 + fi + + # Only change the partition table if sysupgrade -p is set, + # otherwise doing so could interfere with embedded "single storage" + # (e.g SoC boot from SD card) setups, as well as other user + # created storage (like uvol) + if [ -n "$diff" ] && [ "${UPGRADE_OPT_SAVE_PARTITIONS}" = "0" ]; then + # Need to remove partitions before dd, otherwise the partitions + # that are added after will have minor numbers offset + partx -d - "/dev/$diskdev" + + get_image_dd "$1" of="/dev/$diskdev" bs=4096 conv=fsync + + # Separate removal and addtion is necessary; otherwise, partition 1 + # will be missing if it overlaps with the old partition 2 + partx -a - "/dev/$diskdev" + + return 0 + fi + + #iterate over each partition from the image and write it to the boot disk + while read part start size; do + if export_partdevice partdev $part; then + v "Writing image to /dev/$partdev..." + if [ "$part" = "1" ]; then + platform_do_upgrade_efi_system_partition \ + $1 $partdev $start $size || return 1 + else + v "Normal partition, doing DD" + get_image_dd "$1" of="/dev/$partdev" ibs=512 obs=1M skip="$start" \ + count="$size" conv=fsync + fi + else + v "Unable to find partition $part device, skipped." + fi + done < /tmp/partmap.image + + local parttype=ext4 + + if (blkid > /dev/null) && export_partdevice partdev 1; then + part_magic_fat "/dev/$partdev" && parttype=vfat + mount -t $parttype -o rw,noatime "/dev/$partdev" /mnt + if export_partdevice partdev 2; then + THIS_PART_BLKID=$(blkid -o value -s PARTUUID "/dev/${partdev}") + v "Setting rootfs PARTUUID=${THIS_PART_BLKID}" + sed -i "s/\(PARTUUID=\)[a-f0-9-]\+/\1${THIS_PART_BLKID}/ig" \ + /mnt/efi/openwrt/grub.cfg + fi + umount /mnt + fi + # Provide time for the storage medium to flush before system reset + # (despite the sync/umount it appears NVMe etc. do it in the background) + sleep 5 +} diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index 5f9ef21d65..bd75f85996 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -5,28 +5,109 @@ include $(TOPDIR)/rules.mk include $(INCLUDE_DIR)/image.mk -define Image/BuildKernel - $(foreach k,$(filter zImage Image,$(KERNELNAME)), \ - cp $(KDIR)/$(KERNELNAME) $(BIN_DIR)/$(IMG_PREFIX)-$(k) \ +GRUB2_VARIANT = +GRUB_TERMINALS = +GRUB_SERIAL_CONFIG = +GRUB_TERMINAL_CONFIG = +GRUB_CONSOLE_CMDLINE = earlycon + +ifneq ($(CONFIG_GRUB_CONSOLE),) + GRUB_TERMINALS += console +endif + +GRUB_SERIAL:=$(call qstrip,$(CONFIG_GRUB_SERIAL)) + +ifneq ($(GRUB_SERIAL),) + GRUB_SERIAL_CONFIG := serial --unit=0 --speed=$(CONFIG_GRUB_BAUDRATE) --word=8 --parity=no --stop=1 --rtscts=$(if $(CONFIG_GRUB_FLOWCONTROL),on,off) + GRUB_TERMINALS += serial +endif + +ifneq ($(GRUB_TERMINALS),) + GRUB_TERMINAL_CONFIG := terminal_input $(GRUB_TERMINALS); terminal_output $(GRUB_TERMINALS) +endif + +ROOTPART:=$(call qstrip,$(CONFIG_TARGET_ROOTFS_PARTNAME)) +ROOTPART:=$(if $(ROOTPART),$(ROOTPART),PARTUUID=$(IMG_PART_SIGNATURE)-02) +GPT_ROOTPART:=$(call qstrip,$(CONFIG_TARGET_ROOTFS_PARTNAME)) +GPT_ROOTPART:=$(if $(GPT_ROOTPART),$(GPT_ROOTPART),PARTUUID=$(shell echo $(IMG_PART_DISKGUID) | sed 's/00$$/02/')) + +GRUB_TIMEOUT:=$(call qstrip,$(CONFIG_GRUB_TIMEOUT)) +GRUB_TITLE:=$(call qstrip,$(CONFIG_GRUB_TITLE)) + +BOOTOPTS:=$(call qstrip,$(CONFIG_GRUB_BOOTOPTS)) + +define Build/combined + $(INSTALL_DIR) $@.boot/ + $(CP) $(KDIR)/$(KERNEL_NAME) $@.boot/efi/openwrt/ + -$(CP) $(STAGING_DIR_ROOT)/boot/. $@.boot/boot/ + $(if $(filter $(1),efi), + $(INSTALL_DIR) $@.boot/efi/boot + $(CP) $(STAGING_DIR_IMAGE)/grub2/boot$(if $(CONFIG_aarch64),aa64,arm).efi $@.boot/efi/openwrt/ + $(CP) $(STAGING_DIR_IMAGE)/grub2/boot$(if $(CONFIG_aarch64),aa64,arm).efi $@.boot/efi/boot/ ) + FAT_TYPE="32" PADDING="1" SIGNATURE="$(IMG_PART_SIGNATURE)" \ + $(if $(filter $(1),efi),GUID="$(IMG_PART_DISKGUID)") $(SCRIPT_DIR)/gen_image_generic.sh \ + $@ \ + $(CONFIG_TARGET_KERNEL_PARTSIZE) $@.boot \ + $(CONFIG_TARGET_ROOTFS_PARTSIZE) $(IMAGE_ROOTFS) \ + 256 endef -define Image/Build/Initramfs - $(foreach k,$(filter zImage Image,$(KERNELNAME)), \ - cp $(KDIR)/$(k)-initramfs $(BIN_DIR)/$(IMG_PREFIX)-$(k)-initramfs \ - ) +define Build/grub-config + rm -fR $@.boot + $(INSTALL_DIR) $@.boot/efi/openwrt/ + sed \ + -e 's#@SERIAL_CONFIG@#$(strip $(GRUB_SERIAL_CONFIG))#g' \ + -e 's#@TERMINAL_CONFIG@#$(strip $(GRUB_TERMINAL_CONFIG))#g' \ + -e 's#@ROOTPART@#root=$(ROOTPART) rootwait#g' \ + -e 's#@GPT_ROOTPART@#root=$(GPT_ROOTPART) rootwait#g' \ + -e 's#@CMDLINE@#$(BOOTOPTS) $(GRUB_CONSOLE_CMDLINE)#g' \ + -e 's#@TIMEOUT@#$(GRUB_TIMEOUT)#g' \ + -e 's#@TITLE@#$(GRUB_TITLE)#g' \ + -e 's#@KERNEL_NAME@#$(KERNEL_NAME)#g' \ + ./grub-$(1).cfg > $@.boot/efi/openwrt/grub.cfg endef -define Image/Build/gzip - gzip -f9n $(BIN_DIR)/$(IMG_ROOTFS)-$(1).img +define Build/grub-install + rm -fR $@.grub2 + $(INSTALL_DIR) $@.grub2 endef -$(eval $(call Image/gzip-ext4-padded-squashfs)) - -define Image/Build - $(call Image/Build/$(1)) - $(CP) $(KDIR)/root.$(1) $(BIN_DIR)/$(IMG_ROOTFS)-$(1).img - $(call Image/Build/gzip/$(1)) +DEVICE_VARS += GRUB2_VARIANT +define Device/efi-default + IMAGE/rootfs.img := append-rootfs | pad-to $(ROOTFS_PARTSIZE) + IMAGE/rootfs.img.gz := append-rootfs | pad-to $(ROOTFS_PARTSIZE) | gzip + IMAGE/combined.img := grub-config efi | combined efi | grub-install efi | append-metadata + IMAGE/combined.img.gz := grub-config efi | combined efi | grub-install efi | gzip | append-metadata + IMAGE/combined.vmdk := grub-config efi | combined efi | grub-install efi | qemu-image vmdk + ifeq ($(CONFIG_TARGET_IMAGES_GZIP),y) + IMAGES-y := rootfs.img.gz + IMAGES-y += combined.img.gz + else + IMAGES-y := rootfs.img + IMAGES-y += combined.img + endif + ifeq ($(CONFIG_VMDK_IMAGES),y) + IMAGES-y += combined.vmdk + endif + KERNEL := kernel-bin + KERNEL_INSTALL := 1 + IMAGES := $$(IMAGES-y) + ARTIFACTS := $$(ARTIFACTS-y) + SUPPORTED_DEVICES := + ifeq ($(CONFIG_arm),y) + KERNEL_NAME = zImage + endif endef +define Device/generic + $(call Device/efi-default) + DEVICE_TITLE := Generic EFI Boot + GRUB2_VARIANT := generic + FILESYSTEMS := ext4 squashfs + DEVICE_PACKAGES += kmod-amazon-ena kmod-e1000e kmod-vmxnet3 kmod-rtc-rx8025 \ + kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils +endef +TARGET_DEVICES += generic + $(eval $(call BuildImage)) diff --git a/target/linux/armvirt/image/grub-efi.cfg b/target/linux/armvirt/image/grub-efi.cfg new file mode 100644 index 0000000000..fd329e41e0 --- /dev/null +++ b/target/linux/armvirt/image/grub-efi.cfg @@ -0,0 +1,14 @@ +@SERIAL_CONFIG@ +@TERMINAL_CONFIG@ + +set default="0" +set timeout="@TIMEOUT@" + +menuentry "@TITLE@" { + search --set=root --label kernel + linux /efi/openwrt/@KERNEL_NAME@ @GPT_ROOTPART@ @CMDLINE@ noinitrd +} +menuentry "@TITLE@ (failsafe)" { + search --set=root --label kernel + linux /efi/openwrt/@KERNEL_NAME@ failsafe=true @GPT_ROOTPART@ @CMDLINE@ noinitrd +} From eb0e61285d4da910317e082de559337a305fa1dc Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 28 Sep 2022 15:47:30 +1000 Subject: [PATCH 15/37] armvirt: disable LD dead code elimination on ARM32 This interferes with the generation of the EFI stub section for ARM32. As this target is not size constrained, disable the dead code data elimination hack. Signed-off-by: Mathew McBride --- .../221-armvirt-disable-gc_sections.patch | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 target/linux/armvirt/patches-6.1/221-armvirt-disable-gc_sections.patch diff --git a/target/linux/armvirt/patches-6.1/221-armvirt-disable-gc_sections.patch b/target/linux/armvirt/patches-6.1/221-armvirt-disable-gc_sections.patch new file mode 100644 index 0000000000..cb124c1cf8 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/221-armvirt-disable-gc_sections.patch @@ -0,0 +1,23 @@ +From b77c0ecdc7915e5c5c515da1aa6cfaf6f4eb8351 Mon Sep 17 00:00:00 2001 +From: Mathew McBride +Date: Wed, 28 Sep 2022 16:39:31 +1000 +Subject: [PATCH] arm: disable code size reduction measures + (gc-sections,-f*-sections) + +This interferes with the EFI boot stub on armv7l. + +Signed-off-by: Mathew McBride +--- + arch/arm/Kconfig | 1 - + 1 file changed, 1 deletion(-) + +--- a/arch/arm/Kconfig ++++ b/arch/arm/Kconfig +@@ -122,7 +122,6 @@ config ARM + select HAVE_UID16 + select HAVE_VIRT_CPU_ACCOUNTING_GEN + select IRQ_FORCED_THREADING +- select HAVE_LD_DEAD_CODE_DATA_ELIMINATION + select MODULES_USE_ELF_REL + select NEED_DMA_MAP_STATE + select OF_EARLY_FLATTREE if OF From 97c5d317f59e071c9f691add5748a74a75665038 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 19 Jan 2022 03:24:45 +0000 Subject: [PATCH 16/37] armvirt: update README with new image names The introduction of EFI support has changed how armvirt images are generated. The kernel and filesystem binaries can still be used as before with QEMU directly. Signed-off-by: Mathew McBride --- target/linux/armvirt/README | 48 ++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/target/linux/armvirt/README b/target/linux/armvirt/README index b870fe19f7..b4409f8f11 100644 --- a/target/linux/armvirt/README +++ b/target/linux/armvirt/README @@ -1,21 +1,23 @@ -This is intended to be used with OpenWrt project to provide image for use with -QEMU ARM virt machine. +This target generates images that can be used on ARM machines with EFI +support (e.g EDKII/TianoCore or U-Boot with bootefi). + +The kernel and filesystem images can also be used directly by QEMU: Run with qemu-system-arm # boot with initramfs embedded in - qemu-system-arm -nographic -M virt -m 64 -kernel openwrt-armvirt-32-zImage-initramfs + qemu-system-arm -nographic -M virt -m 64 -kernel openwrt-armvirt-32-generic-initramfs-kernel.bin # boot with accel=kvm qemu-system-arm -nographic -M virt,accel=kvm -cpu host -m 64 -kernel - openwrt-armvirt-32-zImage-initramfs + openwrt-armvirt-32-generic-initramfs-kernel.bin # boot with a separate rootfs - qemu-system-arm -nographic -M virt -m 64 -kernel openwrt-armvirt-32-zImage \ - -drive file=openwrt-armvirt-32-root.ext4,format=raw,if=virtio -append 'root=/dev/vda rootwait' + qemu-system-arm -nographic -M virt -m 64 -kernel openwrt-armvirt-32-generic-kernel.bin \ + -drive file=openwrt-armvirt-32-generic-ext4-rootfs.img,format=raw,if=virtio -append 'root=/dev/vda rootwait' # boot with local dir as rootfs - qemu-system-arm -nographic -M virt -m 64 -kernel openwrt-armvirt-32-zImage \ + qemu-system-arm -nographic -M virt -m 64 -kernel openwrt-armvirt-32-generic-kernel.bin \ -fsdev local,id=rootdev,path=root-armvirt/,security_model=none \ -device virtio-9p-pci,fsdev=rootdev,mount_tag=/dev/root \ -append 'rootflags=trans=virtio,version=9p2000.L,cache=loose rootfstype=9p' @@ -37,4 +39,34 @@ Run with kvmtool The multi-platform ARMv8 target can be used with QEMU: qemu-system-aarch64 -machine virt -cpu cortex-a57 -nographic \ - -kernel openwrt-armvirt-64-Image-initramfs \ + -kernel openwrt-armvirt-64-generic-initramfs-kernel.bin \ + +With a EDKII or U-Boot binary for the QEMU ARM virtual machines, you can use these +images in EFI mode: + +32-bit: +gunzip -c bin/targets/armvirt/32/openwrt-armvirt-32-generic-ext4-combined.img.gz > openwrt-arm-32.img +qemu-system-arm -nographic \ + -cpu cortex-a15 -machine virt \ + -bios QEMU_EFI_32.fd \ + -smp 1 -m 1024 \ + -device virtio-rng-pci \ + -drive file=openwrt-arm-32.img,format=raw,index=0,media=disk \ + -netdev user,id=testlan -net nic,netdev=testlan \ + -netdev user,id=testwan -net nic,netdev=testwan + +64-bit: +gunzip -c bin/targets/armvirt/64/openwrt-armvirt-64-generic-ext4-combined.img.gz > openwrt-arm-64.img +qemu-system-aarch64 -nographic \ + -cpu cortex-a53 -machine virt \ + -bios QEMU_EFI_64.fd \ + -smp 1 -m 1024 \ + -device virtio-rng-pci \ + -drive file=openwrt-arm-64.img,format=raw,index=0,media=disk \ + -netdev user,id=testlan -net nic,netdev=testlan \ + -netdev user,id=testwan -net nic,netdev=testwan + +One can find EFI/BIOS binaries from: +- Compile mainline U-Boot for the QEMU ARM virtual machine (qemu_arm_defconfig/qemu_arm64_defconfig) +- From distribution packages (such as qemu-efi-arm and qemu-efi-aarch64 in Debian) +- Community builds, like retrage/edk2-nightly: https://retrage.github.io/edk2-nightly/ From 8f29b1573ddf3b7ed7c53bee1a7d55e574806205 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 24 Feb 2021 04:53:40 +0000 Subject: [PATCH 17/37] grub2: enable EFI for armvirt This adds a separate package for EFI on Arm SystemReady compatible machines. 32-bit Arm UEFI is supported as well. It is very similar to x86-64 EFI setup, without the need for BIOS backward compatibility and slightly different default modules. Signed-off-by: Mathew McBride --- config/Config-images.in | 9 ++--- package/boot/grub2/Makefile | 40 +++++++++++++++++---- package/boot/grub2/files/grub-early-gpt.cfg | 2 ++ 3 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 package/boot/grub2/files/grub-early-gpt.cfg diff --git a/config/Config-images.in b/config/Config-images.in index 8c4616f37c..cbf1e9f26d 100644 --- a/config/Config-images.in +++ b/config/Config-images.in @@ -204,11 +204,12 @@ menu "Target Images" config GRUB_EFI_IMAGES bool "Build GRUB EFI images (Linux x86 or x86_64 host only)" - depends on TARGET_x86 + depends on TARGET_x86 || TARGET_armvirt depends on TARGET_ROOTFS_EXT4FS || TARGET_ROOTFS_JFFS2 || TARGET_ROOTFS_SQUASHFS - select PACKAGE_grub2 - select PACKAGE_grub2-efi - select PACKAGE_grub2-bios-setup + select PACKAGE_grub2 if TARGET_x86 + select PACKAGE_grub2-efi if TARGET_x86 + select PACKAGE_grub2-bios-setup if TARGET_x86 + select PACKAGE_grub2-efi-arm if TARGET_armvirt select PACKAGE_kmod-fs-vfat default y diff --git a/package/boot/grub2/Makefile b/package/boot/grub2/Makefile index 0613f46f99..50bf96360d 100644 --- a/package/boot/grub2/Makefile +++ b/package/boot/grub2/Makefile @@ -33,14 +33,15 @@ include $(INCLUDE_DIR)/package.mk define Package/grub2/Default CATEGORY:=Boot Loaders SECTION:=boot - TITLE:=GRand Unified Bootloader ($(1)) + TITLE:=GRand Unified Bootloader ($(2)) URL:=http://www.gnu.org/software/grub/ - DEPENDS:=@TARGET_x86 - VARIANT:=$(1) + DEPENDS:=@TARGET_$(1) + VARIANT:=$(2) endef -Package/grub2=$(call Package/grub2/Default,pc) -Package/grub2-efi=$(call Package/grub2/Default,efi) +Package/grub2=$(call Package/grub2/Default,x86,pc) +Package/grub2-efi=$(call Package/grub2/Default,x86,efi) +Package/grub2-efi-arm=$(call Package/grub2/Default,armvirt,efi) define Package/grub2-editenv CATEGORY:=Utilities @@ -107,6 +108,10 @@ ifneq ($(BUILD_VARIANT),none) MAKE_PATH := grub-core endif +ifeq ($(CONFIG_arm),y) + TARGET_CFLAGS := $(filter-out -mfloat-abi=hard,$(TARGET_CFLAGS)) +endif + define Host/Configure $(SED) 's,(RANLIB),(TARGET_RANLIB),' $(HOST_BUILD_DIR)/grub-core/Makefile.in $(Host/Configure/Default) @@ -162,9 +167,31 @@ define Package/grub2-efi/install -O $(CONFIG_ARCH)-efi \ -c ./files/grub-early.cfg \ -o $(STAGING_DIR_IMAGE)/grub2/iso-boot$(if $(CONFIG_x86_64),x64,ia32).efi \ - at_keyboard boot chain configfile fat iso9660 linux ls part_msdos part_gpt reboot serial test efi_gop efi_uga + boot chain configfile fat iso9660 linux ls part_msdos part_gpt reboot serial test efi_gop efi_uga endef +define Package/grub2-efi-arm/install + $(INSTALL_DIR) $(STAGING_DIR_IMAGE)/grub2 + cp ./files/grub-early-gpt.cfg $(PKG_BUILD_DIR)/grub-early.cfg + $(STAGING_DIR_HOST)/bin/grub-mkimage \ + -d $(PKG_BUILD_DIR)/grub-core \ + -p /boot/grub \ + -O arm$(if $(CONFIG_aarch64),64,)-efi \ + -c $(PKG_BUILD_DIR)/grub-early.cfg \ + -o $(STAGING_DIR_IMAGE)/grub2/boot$(if $(CONFIG_aarch64),aa64,arm).efi \ + boot chain configfile fat linux ls part_gpt part_msdos reboot search \ + search_fs_uuid search_label serial efi_gop lsefi minicmd ext2 + $(STAGING_DIR_HOST)/bin/grub-mkimage \ + -d $(PKG_BUILD_DIR)/grub-core \ + -p /boot/grub \ + -O arm$(if $(CONFIG_aarch64),64,)-efi \ + -c ./files/grub-early.cfg \ + -o $(STAGING_DIR_IMAGE)/grub2/iso-bootaa$(if $(CONFIG_aarch64),aa64,arm).efi \ + boot chain configfile fat iso9660 linux ls lsefi minicmd part_msdos part_gpt \ + reboot serial test efi_gop +endef + + define Package/grub2-editenv/install $(INSTALL_DIR) $(1)/usr/sbin $(INSTALL_BIN) $(PKG_BUILD_DIR)/grub-editenv $(1)/usr/sbin/ @@ -178,5 +205,6 @@ endef $(eval $(call HostBuild)) $(eval $(call BuildPackage,grub2)) $(eval $(call BuildPackage,grub2-efi)) +$(eval $(call BuildPackage,grub2-efi-arm)) $(eval $(call BuildPackage,grub2-editenv)) $(eval $(call BuildPackage,grub2-bios-setup)) diff --git a/package/boot/grub2/files/grub-early-gpt.cfg b/package/boot/grub2/files/grub-early-gpt.cfg new file mode 100644 index 0000000000..c295d1f7d7 --- /dev/null +++ b/package/boot/grub2/files/grub-early-gpt.cfg @@ -0,0 +1,2 @@ +search --set=root --label kernel +configfile ($root)/efi/openwrt/grub.cfg From 701d774f54aef2f9fe3c584700773dcb260dd03c Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Thu, 20 Apr 2023 06:36:50 +0000 Subject: [PATCH 18/37] scripts: gen_image_generic: allow the partition types to be set The use case for this is to set the kernel partition as the EFI system partition. Versions of U-Boot with the EFI boot manager (eficonfig and efidebug commands) will store their boot order data on the ESP. Signed-off-by: Mathew McBride --- scripts/gen_image_generic.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/gen_image_generic.sh b/scripts/gen_image_generic.sh index 5c88dcea80..11e40f3886 100755 --- a/scripts/gen_image_generic.sh +++ b/scripts/gen_image_generic.sh @@ -9,8 +9,10 @@ fi OUTPUT="$1" KERNELSIZE="$2" KERNELDIR="$3" +KERNELPARTTYPE=${KERNELPARTTYPE:-83} ROOTFSSIZE="$4" ROOTFSIMAGE="$5" +ROOTFSPARTTYPE=${ROOTFSPARTTYPE:-83} ALIGN="$6" rm -f "$OUTPUT" @@ -19,7 +21,7 @@ head=16 sect=63 # create partition table -set $(ptgen -o "$OUTPUT" -h $head -s $sect ${GUID:+-g} -p "${KERNELSIZE}m${PARTOFFSET:+@$PARTOFFSET}" -p "${ROOTFSSIZE}m" ${ALIGN:+-l $ALIGN} ${SIGNATURE:+-S 0x$SIGNATURE} ${GUID:+-G $GUID}) +set $(ptgen -o "$OUTPUT" -h $head -s $sect ${GUID:+-g} -t "${KERNELPARTTYPE}" -p "${KERNELSIZE}m${PARTOFFSET:+@$PARTOFFSET}" -t "${ROOTFSPARTTYPE}" -p "${ROOTFSSIZE}m" ${ALIGN:+-l $ALIGN} ${SIGNATURE:+-S 0x$SIGNATURE} ${GUID:+-G $GUID}) KERNELOFFSET="$(($1 / 512))" KERNELSIZE="$2" From 9a76b99c1bd781248c18d69abe570f35932db8a3 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Thu, 20 Apr 2023 06:38:22 +0000 Subject: [PATCH 19/37] armvirt: set kernel partition as the EFI system partition U-Boot with EFI boot manager functionality will store EFI boot order data on the ESP in the ubootefi.var file. Signed-off-by: Mathew McBride --- target/linux/armvirt/image/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index bd75f85996..50c993b522 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -45,7 +45,7 @@ define Build/combined $(CP) $(STAGING_DIR_IMAGE)/grub2/boot$(if $(CONFIG_aarch64),aa64,arm).efi $@.boot/efi/openwrt/ $(CP) $(STAGING_DIR_IMAGE)/grub2/boot$(if $(CONFIG_aarch64),aa64,arm).efi $@.boot/efi/boot/ ) - FAT_TYPE="32" PADDING="1" SIGNATURE="$(IMG_PART_SIGNATURE)" \ + KERNELPARTTYPE=ef FAT_TYPE="32" PADDING="1" SIGNATURE="$(IMG_PART_SIGNATURE)" \ $(if $(filter $(1),efi),GUID="$(IMG_PART_DISKGUID)") $(SCRIPT_DIR)/gen_image_generic.sh \ $@ \ $(CONFIG_TARGET_KERNEL_PARTSIZE) $@.boot \ From 3d99314569a059a1d5e015086e534b3e04ff2097 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Mon, 24 Jan 2022 23:16:56 +0000 Subject: [PATCH 20/37] armvirt: remove model name override Now that armvirt has been expanded to boot on more generic ARM machines, remove the board and model name override. Signed-off-by: Mathew McBride --- .../linux/armvirt/base-files/etc/board.d/00_model | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 target/linux/armvirt/base-files/etc/board.d/00_model diff --git a/target/linux/armvirt/base-files/etc/board.d/00_model b/target/linux/armvirt/base-files/etc/board.d/00_model deleted file mode 100644 index bfaa45f59c..0000000000 --- a/target/linux/armvirt/base-files/etc/board.d/00_model +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (C) 2015 OpenWrt.org -# Copyright (C) 2016 Yousong Zhou - -. /lib/functions/uci-defaults.sh - -board_config_update - -ucidef_set_board_id "armvirt" -ucidef_set_model_name "QEMU ARM Virtual Machine" - -board_config_flush - -exit 0 From 71e56b2ff1e8aeb3205784c0b5f8ca6ba0fbbf63 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Tue, 1 Jun 2021 01:48:25 +0000 Subject: [PATCH 21/37] build: use 128MiB as the boot/kernel partition size on armvirt target The nominal partition type for EFI boot partitions is FAT32, which has a minimum size of 32MiB on a 512-byte-sector block device. To ensure that the boot partition is created as FAT32 set a size well above this minimum. A useful discussion about EFI partition sizes can be found here: https://superuser.com/questions/1310927/what-is-the-absolute-minimum-size-a-uefi-system-partition-can-be I have found 128MiB works pretty consistently across both tools (mkfs.fat) and firmwares (EDKII) Signed-off-by: Mathew McBride --- config/Config-images.in | 1 + 1 file changed, 1 insertion(+) diff --git a/config/Config-images.in b/config/Config-images.in index cbf1e9f26d..396f1dd3e3 100644 --- a/config/Config-images.in +++ b/config/Config-images.in @@ -292,6 +292,7 @@ menu "Target Images" depends on USES_BOOT_PART default 8 if TARGET_apm821xx_sata default 64 if TARGET_bcm27xx + default 128 if TARGET_armvirt default 16 config TARGET_ROOTFS_PARTSIZE From f899e0e024825861e129b0e8fbfb31c1d614273a Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 12 Jan 2022 05:53:55 +0000 Subject: [PATCH 22/37] build: enable vmdk/vmware images for arm64 target This is useful for VMware's ARM64 products, e.g Fusion for M1/ARM Macs. Signed-off-by: Mathew McBride --- config/Config-images.in | 2 +- target/linux/armvirt/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/Config-images.in b/config/Config-images.in index 396f1dd3e3..d92c31ec5f 100644 --- a/config/Config-images.in +++ b/config/Config-images.in @@ -267,7 +267,7 @@ menu "Target Images" config VMDK_IMAGES bool "Build VMware image files (VMDK)" - depends on TARGET_x86 + depends on TARGET_x86 || TARGET_armvirt depends on GRUB_IMAGES || GRUB_EFI_IMAGES select PACKAGE_kmod-e1000 diff --git a/target/linux/armvirt/Makefile b/target/linux/armvirt/Makefile index 36c91a077f..ff362428a9 100644 --- a/target/linux/armvirt/Makefile +++ b/target/linux/armvirt/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk BOARD:=armvirt BOARDNAME:=QEMU ARM Virtual Machine FEATURES:=fpu pci pcie rtc usb boot-part rootfs-part -FEATURES+=cpiogz ext4 ramdisk squashfs targz +FEATURES+=cpiogz ext4 ramdisk squashfs targz vmdk KERNEL_PATCHVER:=5.15 KERNEL_TESTING_PATCHVER:=6.1 From cb3bbbf00cfb465de3333e4b84e8da9138985595 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Thu, 9 Jun 2022 04:51:59 +0000 Subject: [PATCH 23/37] armvirt: add ACPI support ACPI support is required for Arm 'SystemReady' server and workstation systems (and as an option on embedded platforms). These config changes allow OpenWrt to boot in a QEMU virtual machine with a UEFI/EDKII 'BIOS', but with no other hardware enabled yet. Signed-off-by: Mathew McBride --- target/linux/armvirt/64/config-6.1 | 3 + target/linux/armvirt/config-6.1 | 143 +++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/target/linux/armvirt/64/config-6.1 b/target/linux/armvirt/64/config-6.1 index 5ef4c5d7ff..f3fa13f06f 100644 --- a/target/linux/armvirt/64/config-6.1 +++ b/target/linux/armvirt/64/config-6.1 @@ -106,6 +106,7 @@ CONFIG_GENERIC_FIND_FIRST_BIT=y CONFIG_GPIO_GENERIC=y CONFIG_GPIO_GENERIC_PLATFORM=y CONFIG_HDMI=y +# CONFIG_HISI_PMU is not set CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_ARM_SMCCC_TRNG=y CONFIG_HW_RANDOM_VIRTIO=y @@ -146,6 +147,8 @@ CONFIG_SPARSEMEM=y CONFIG_SPARSEMEM_EXTREME=y CONFIG_SPARSEMEM_VMEMMAP=y CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +# CONFIG_SPI_HISI_KUNPENG is not set +# CONFIG_SPI_HISI_SFC_V3XX is not set CONFIG_SYNC_FILE=y CONFIG_SYSCTL_EXCEPTION_TRACE=y CONFIG_THREAD_INFO_IN_TASK=y diff --git a/target/linux/armvirt/config-6.1 b/target/linux/armvirt/config-6.1 index 04da68ea1b..6cbc963913 100644 --- a/target/linux/armvirt/config-6.1 +++ b/target/linux/armvirt/config-6.1 @@ -1,55 +1,150 @@ +CONFIG_64BIT=y CONFIG_9P_FS=y # CONFIG_9P_FS_POSIX_ACL is not set # CONFIG_9P_FS_SECURITY is not set +# CONFIG_A64FX_DIAG is not set +CONFIG_ACPI=y +CONFIG_ACPI_AC=y +CONFIG_ACPI_APEI=y +CONFIG_ACPI_APEI_EINJ=y +# CONFIG_ACPI_APEI_ERST_DEBUG is not set +CONFIG_ACPI_APEI_GHES=y +CONFIG_ACPI_APEI_MEMORY_FAILURE=y +CONFIG_ACPI_APEI_PCIEAER=y +CONFIG_ACPI_BATTERY=y +# CONFIG_ACPI_BGRT is not set +CONFIG_ACPI_CCA_REQUIRED=y +CONFIG_ACPI_CONTAINER=y +CONFIG_ACPI_CPPC_CPUFREQ=y +# CONFIG_ACPI_DEBUG is not set +# CONFIG_ACPI_DEBUGGER is not set +# CONFIG_ACPI_DOCK is not set +# CONFIG_ACPI_EC_DEBUGFS is not set +CONFIG_ACPI_FAN=y +CONFIG_ACPI_GENERIC_GSI=y +CONFIG_ACPI_GTDT=y +CONFIG_ACPI_HOTPLUG_CPU=y +CONFIG_ACPI_I2C_OPREGION=y +CONFIG_ACPI_IORT=y +CONFIG_ACPI_MCFG=y +# CONFIG_ACPI_PCI_SLOT is not set +# CONFIG_ACPI_PFRUT is not set +CONFIG_ACPI_PPTT=y +CONFIG_ACPI_PRMT=y +CONFIG_ACPI_PROCESSOR=y +CONFIG_ACPI_PROCESSOR_IDLE=y +CONFIG_ACPI_REDUCED_HARDWARE_ONLY=y +CONFIG_ACPI_SPCR_TABLE=y +CONFIG_ACPI_THERMAL=y +# CONFIG_ACPI_TINY_POWER_BUTTON is not set +# CONFIG_ALIBABA_UNCORE_DRW_PMU is not set CONFIG_ARCH_DMA_ADDR_T_64BIT=y CONFIG_ARCH_FORCE_MAX_ORDER=11 CONFIG_ARCH_HIBERNATION_POSSIBLE=y CONFIG_ARCH_KEEP_MEMBLOCK=y +CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y +CONFIG_ARCH_MMAP_RND_BITS=18 +CONFIG_ARCH_MMAP_RND_BITS_MAX=24 +CONFIG_ARCH_MMAP_RND_BITS_MIN=18 +CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=11 +CONFIG_ARCH_PROC_KCORE_TEXT=y CONFIG_ARCH_SPARSEMEM_ENABLE=y +CONFIG_ARCH_STACKWALK=y CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARCH_WANTS_NO_INSTR=y +CONFIG_ARM64=y +CONFIG_ARM64_4K_PAGES=y +# CONFIG_ARM64_ACPI_PARKING_PROTOCOL is not set +CONFIG_ARM64_LD_HAS_FIX_ERRATUM_843419=y +CONFIG_ARM64_PAGE_SHIFT=12 +CONFIG_ARM64_PA_BITS=48 +CONFIG_ARM64_PA_BITS_48=y +CONFIG_ARM64_TAGGED_ADDR_ABI=y +CONFIG_ARM64_VA_BITS=39 +CONFIG_ARM64_VA_BITS_39=y CONFIG_ARM_AMBA=y CONFIG_ARM_ARCH_TIMER=y CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +# CONFIG_ARM_DMC620_PMU is not set CONFIG_ARM_GIC=y CONFIG_ARM_GIC_V2M=y CONFIG_ARM_GIC_V3=y CONFIG_ARM_GIC_V3_ITS=y CONFIG_ARM_GIC_V3_ITS_PCI=y CONFIG_ARM_PSCI_FW=y +# CONFIG_ARM_SMMU_V3_PMU is not set +CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y CONFIG_BALLOON_COMPACTION=y CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_MQ_PCI=y CONFIG_BLK_MQ_VIRTIO=y +CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y CONFIG_CLONE_BACKWARDS=y CONFIG_COMMON_CLK=y +# CONFIG_COMPAT_32BIT_TIME is not set +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_PM=y CONFIG_CPU_RMAP=y CONFIG_CRC16=y CONFIG_CRYPTO_CRC32=y CONFIG_CRYPTO_CRC32C=y +CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y CONFIG_CRYPTO_RNG2=y CONFIG_DCACHE_WORD_ACCESS=y CONFIG_DEBUG_BUGVERBOSE=y CONFIG_DMADEVICES=y +CONFIG_DMA_ACPI=y +CONFIG_DMA_DIRECT_REMAP=y CONFIG_DMA_ENGINE=y CONFIG_DMA_OF=y CONFIG_DMA_REMAP=y +CONFIG_DMI=y +CONFIG_DMIID=y +CONFIG_DMI_SYSFS=y CONFIG_DTC=y CONFIG_EDAC_SUPPORT=y +CONFIG_EFI=y +CONFIG_EFIVAR_FS=y +CONFIG_EFI_ARMSTUB_DTB_LOADER=y +# CONFIG_EFI_BOOTLOADER_CONTROL is not set +# CONFIG_EFI_CAPSULE_LOADER is not set +# CONFIG_EFI_COCO_SECRET is not set +# CONFIG_EFI_CUSTOM_SSDT_OVERLAYS is not set +# CONFIG_EFI_DISABLE_PCI_DMA is not set +# CONFIG_EFI_DISABLE_RUNTIME is not set +CONFIG_EFI_EARLYCON=y +CONFIG_EFI_ESRT=y +CONFIG_EFI_GENERIC_STUB=y +# CONFIG_EFI_GENERIC_STUB_INITRD_CMDLINE_LOADER is not set +CONFIG_EFI_PARAMS_FROM_FDT=y +CONFIG_EFI_RUNTIME_WRAPPERS=y +CONFIG_EFI_STUB=y +# CONFIG_EFI_TEST is not set +# CONFIG_EFI_ZBOOT is not set CONFIG_EXT4_FS=y CONFIG_F2FS_FS=y CONFIG_FAILOVER=y +CONFIG_FB_EFI=y CONFIG_FIX_EARLYCON_MEM=y +CONFIG_FONT_8x16=y +CONFIG_FONT_AUTOSELECT=y +CONFIG_FONT_SUPPORT=y +CONFIG_FRAME_POINTER=y CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y CONFIG_FW_LOADER_PAGED_BUF=y CONFIG_GENERIC_ALLOCATOR=y CONFIG_GENERIC_ARCH_TOPOLOGY=y CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y CONFIG_GENERIC_CLOCKEVENTS=y CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y CONFIG_GENERIC_CPU_AUTOPROBE=y CONFIG_GENERIC_CPU_VULNERABILITIES=y +CONFIG_GENERIC_CSUM=y CONFIG_GENERIC_EARLY_IOREMAP=y +CONFIG_GENERIC_FIND_FIRST_BIT=y CONFIG_GENERIC_GETTIMEOFDAY=y CONFIG_GENERIC_IDLE_POLL_SETUP=y CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK=y @@ -66,7 +161,9 @@ CONFIG_GENERIC_STRNCPY_FROM_USER=y CONFIG_GENERIC_STRNLEN_USER=y CONFIG_GENERIC_TIME_VSYSCALL=y CONFIG_GPIOLIB_IRQCHIP=y +CONFIG_GPIO_ACPI=y CONFIG_GPIO_CDEV=y +# CONFIG_GPIO_HISI is not set CONFIG_GPIO_PL061=y CONFIG_HANDLE_DOMAIN_IRQ=y CONFIG_HARDIRQS_SW_RESEND=y @@ -74,13 +171,21 @@ CONFIG_HAS_DMA=y CONFIG_HAS_IOMEM=y CONFIG_HAS_IOPORT_MAP=y CONFIG_HOTPLUG_CPU=y +CONFIG_HOTPLUG_PCI_ACPI=y CONFIG_HVC_DRIVER=y +CONFIG_HZ_PERIODIC=y +# CONFIG_I2C_AMD_MP2 is not set +CONFIG_I2C_HID_ACPI=y +# CONFIG_I2C_HISI is not set +CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +# CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set CONFIG_INITRAMFS_SOURCE="" CONFIG_IRQCHIP=y CONFIG_IRQ_DOMAIN=y CONFIG_IRQ_DOMAIN_HIERARCHY=y CONFIG_IRQ_FORCED_THREADING=y CONFIG_IRQ_WORK=y +# CONFIG_ISCSI_IBFT is not set CONFIG_JBD2=y CONFIG_LIBFDT=y CONFIG_LOCK_DEBUGGING_SUPPORT=y @@ -88,8 +193,12 @@ CONFIG_LOCK_SPIN_ON_OWNER=y CONFIG_MEMFD_CREATE=y CONFIG_MEMORY_BALLOON=y CONFIG_MIGRATION=y +# CONFIG_MLXBF_GIGE is not set +CONFIG_MMC_SDHCI_ACPI=y +CONFIG_MODULES_USE_ELF_RELA=y CONFIG_MUTEX_SPIN_ON_OWNER=y CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_NEED_SG_DMA_LENGTH=y CONFIG_NET_9P=y # CONFIG_NET_9P_DEBUG is not set # CONFIG_NET_9P_FD is not set @@ -97,6 +206,7 @@ CONFIG_NET_9P_VIRTIO=y CONFIG_NET_FAILOVER=y CONFIG_NET_FLOW_LIMIT=y CONFIG_NLS=y +CONFIG_NR_CPUS=256 CONFIG_NVMEM=y CONFIG_OF=y CONFIG_OF_ADDRESS=y @@ -109,40 +219,71 @@ CONFIG_PADATA=y CONFIG_PAGE_REPORTING=y CONFIG_PARTITION_PERCPU=y CONFIG_PCI=y +# CONFIG_PCIE_HISI_ERR is not set CONFIG_PCI_DOMAINS=y CONFIG_PCI_DOMAINS_GENERIC=y CONFIG_PCI_ECAM=y CONFIG_PCI_HOST_COMMON=y CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCI_LABEL=y CONFIG_PCI_MSI=y CONFIG_PCI_MSI_IRQ_DOMAIN=y CONFIG_PGTABLE_LEVELS=3 CONFIG_PHYS_ADDR_T_64BIT=y +# CONFIG_PMIC_OPREGION is not set +CONFIG_PNP=y +CONFIG_PNPACPI=y +CONFIG_PNP_DEBUG_MESSAGES=y +CONFIG_POWER_RESET=y +CONFIG_POWER_SUPPLY=y CONFIG_PTP_1588_CLOCK_OPTIONAL=y +CONFIG_QUEUED_RWLOCKS=y +CONFIG_QUEUED_SPINLOCKS=y CONFIG_RATIONAL=y +# CONFIG_RESET_ATTACK_MITIGATION is not set CONFIG_RFS_ACCEL=y +CONFIG_RODATA_FULL_DEFAULT_ENABLED=y CONFIG_RPS=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_EFI=y CONFIG_RTC_DRV_PL031=y CONFIG_RWSEM_SPIN_ON_OWNER=y CONFIG_SCSI=y CONFIG_SCSI_COMMON=y CONFIG_SCSI_VIRTIO=y CONFIG_SERIAL_8250_FSL=y +CONFIG_SERIAL_8250_PNP=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_SERIAL_MCTRL_GPIO=y CONFIG_SG_POOL=y CONFIG_SMP=y CONFIG_SOCK_RX_QUEUE_MAPPING=y +CONFIG_SPARSEMEM=y +CONFIG_SPARSEMEM_EXTREME=y +CONFIG_SPARSEMEM_VMEMMAP=y +CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y CONFIG_SPARSE_IRQ=y CONFIG_SRCU=y +# CONFIG_SURFACE_PLATFORMS is not set CONFIG_SWIOTLB=y +CONFIG_SYSCTL_EXCEPTION_TRACE=y +CONFIG_SYSFB=y +# CONFIG_SYSFB_SIMPLEFB is not set +CONFIG_THERMAL=y +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +CONFIG_THERMAL_EMERGENCY_POWEROFF_DELAY_MS=0 +CONFIG_THERMAL_GOV_STEP_WISE=y +CONFIG_THERMAL_OF=y +CONFIG_THREAD_INFO_IN_TASK=y CONFIG_TICK_CPU_ACCOUNTING=y +CONFIG_TIMER_ACPI=y CONFIG_TIMER_OF=y CONFIG_TIMER_PROBE=y CONFIG_TREE_RCU=y CONFIG_TREE_SRCU=y +CONFIG_UCS2_STRING=y +CONFIG_UNMAP_KERNEL_AT_EL0=y CONFIG_USB_SUPPORT=y CONFIG_VIRTIO=y CONFIG_VIRTIO_BALLOON=y @@ -154,4 +295,6 @@ CONFIG_VIRTIO_NET=y CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_PCI_LIB=y +CONFIG_VMAP_STACK=y CONFIG_XPS=y +CONFIG_ZONE_DMA32=y From 54bb95f879aaa62c4253d30390e77bc8180f4ed7 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 31 May 2023 05:47:41 +0000 Subject: [PATCH 24/37] armvirt: add options and driver modules for NXP Layerscape DPAA2 platform Tested with a Traverse Technologies Ten64 (LS1088A) board. Signed-off-by: Mathew McBride --- target/linux/armvirt/64/config-6.1 | 59 +++++++++++++++++- target/linux/armvirt/config-6.1 | 21 ++++++- target/linux/armvirt/image/Makefile | 3 +- target/linux/armvirt/modules.mk | 96 +++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 target/linux/armvirt/modules.mk diff --git a/target/linux/armvirt/64/config-6.1 b/target/linux/armvirt/64/config-6.1 index f3fa13f06f..6bb43c22ca 100644 --- a/target/linux/armvirt/64/config-6.1 +++ b/target/linux/armvirt/64/config-6.1 @@ -1,9 +1,11 @@ CONFIG_64BIT=y +CONFIG_ARCH_LAYERSCAPE=y CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y CONFIG_ARCH_MMAP_RND_BITS=18 CONFIG_ARCH_MMAP_RND_BITS_MAX=24 CONFIG_ARCH_MMAP_RND_BITS_MIN=18 CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=11 +CONFIG_ARCH_NXP=y CONFIG_ARCH_PROC_KCORE_TEXT=y CONFIG_ARCH_STACKWALK=y CONFIG_ARCH_VEXPRESS=y @@ -32,21 +34,33 @@ CONFIG_ARM64_PTR_AUTH_KERNEL=y CONFIG_ARM64_SME=y CONFIG_ARM64_SVE=y CONFIG_ARM64_TAGGED_ADDR_ABI=y -CONFIG_ARM64_VA_BITS=39 -CONFIG_ARM64_VA_BITS_39=y +CONFIG_ARM64_VA_BITS=48 +CONFIG_ARM64_VA_BITS_48=y CONFIG_ARM64_WORKAROUND_CLEAN_CACHE=y CONFIG_ARM64_WORKAROUND_REPEAT_TLBI=y CONFIG_ARM64_WORKAROUND_SPECULATIVE_AT=y CONFIG_ARM_ARCH_TIMER_OOL_WORKAROUND=y +# CONFIG_ARM_DMC620_PMU is not set CONFIG_ARM_SBSA_WATCHDOG=y +CONFIG_ARM_SMC_WATCHDOG=y +CONFIG_ARM_SMMU=y +# CONFIG_ARM_SMMU_DISABLE_BYPASS_BY_DEFAULT is not set +# CONFIG_ARM_SMMU_LEGACY_DT_BINDINGS is not set +CONFIG_ARM_SMMU_V3=y +# CONFIG_ARM_SMMU_V3_PMU is not set +# CONFIG_ARM_SMMU_V3_SVA is not set CONFIG_ATOMIC64_SELFTEST=y CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BLK_PM=y CONFIG_CAVIUM_TX2_ERRATUM_219=y CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y +CONFIG_CLK_LS1028A_PLLDIG=y +CONFIG_CLK_QORIQ=y CONFIG_CLK_SP810=y CONFIG_CLK_VEXPRESS_OSC=y +# CONFIG_COMMON_CLK_FSL_FLEXSPI is not set +# CONFIG_COMMON_CLK_FSL_SAI is not set # CONFIG_COMPAT_32BIT_TIME is not set CONFIG_CPU_IDLE=y CONFIG_CPU_IDLE_GOV_MENU=y @@ -63,6 +77,7 @@ CONFIG_CRYPTO_BLAKE2S=y CONFIG_CRYPTO_CHACHA20=y CONFIG_CRYPTO_CHACHA20_NEON=y CONFIG_CRYPTO_CRYPTD=y +# CONFIG_CRYPTO_DEV_FSL_DPAA2_CAAM is not set CONFIG_CRYPTO_GHASH_ARM64_CE=y CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y CONFIG_CRYPTO_LIB_CHACHA_GENERIC=y @@ -98,13 +113,21 @@ CONFIG_FB_CFB_IMAGEBLIT=y CONFIG_FB_CMDLINE=y CONFIG_FB_MODE_HELPERS=y CONFIG_FRAME_POINTER=y +CONFIG_FRAME_WARN=2048 +# CONFIG_FSL_DPAA is not set +# CONFIG_FSL_DPAA2_QDMA is not set CONFIG_FSL_ERRATUM_A008585=y +# CONFIG_FSL_FMAN is not set +# CONFIG_FSL_IMX8_DDR_PMU is not set +# CONFIG_FSL_PQ_MDIO is not set CONFIG_FUJITSU_ERRATUM_010001=y CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y CONFIG_GENERIC_CSUM=y CONFIG_GENERIC_FIND_FIRST_BIT=y +# CONFIG_GIANFAR is not set CONFIG_GPIO_GENERIC=y CONFIG_GPIO_GENERIC_PLATFORM=y +CONFIG_GPIO_MPC8XXX=y CONFIG_HDMI=y # CONFIG_HISI_PMU is not set CONFIG_HW_RANDOM=y @@ -113,7 +136,19 @@ CONFIG_HW_RANDOM_VIRTIO=y CONFIG_I2C=y CONFIG_I2C_ALGOBIT=y CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_IMX=y +# CONFIG_I2C_SLAVE_TESTUNIT is not set CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 +# CONFIG_IMX2_WDT is not set +# CONFIG_INPUT_IBM_PANEL is not set +# CONFIG_IOMMU_DEBUGFS is not set +# CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set +CONFIG_IOMMU_DEFAULT_DMA_STRICT=y +CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y +# CONFIG_IOMMU_IO_PGTABLE_ARMV7S is not set +# CONFIG_IOMMU_IO_PGTABLE_DART is not set +# CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST is not set +CONFIG_IOMMU_SUPPORT=y CONFIG_KCMP=y CONFIG_LCD_CLASS_DEVICE=m # CONFIG_LCD_PLATFORM is not set @@ -122,6 +157,12 @@ CONFIG_MFD_SYSCON=y CONFIG_MFD_VEXPRESS_SYSREG=y CONFIG_MMC=y CONFIG_MMC_ARMMMCI=y +CONFIG_MMC_RICOH_MMC=y +CONFIG_MMC_SDHCI=y +CONFIG_MMC_SDHCI_ACPI=y +CONFIG_MMC_SDHCI_OF_ESDHC=y +CONFIG_MMC_SDHCI_PCI=y +CONFIG_MMC_SDHCI_PLTFM=y CONFIG_MODULES_USE_ELF_RELA=y CONFIG_NEED_SG_DMA_LENGTH=y CONFIG_NO_HZ=y @@ -129,6 +170,10 @@ CONFIG_NO_HZ_COMMON=y CONFIG_NO_HZ_IDLE=y CONFIG_NR_CPUS=64 CONFIG_NVIDIA_CARMEL_CNP_ERRATUM=y +# CONFIG_NVMEM_LAYERSCAPE_SFP is not set +CONFIG_PCIE_LAYERSCAPE=y +CONFIG_PCI_LAYERSCAPE=y +# CONFIG_PHY_FSL_LYNX_28G is not set CONFIG_PM=y CONFIG_PM_CLK=y CONFIG_PM_GENERIC_DOMAINS=y @@ -136,26 +181,34 @@ CONFIG_PM_GENERIC_DOMAINS_OF=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_VEXPRESS=y CONFIG_POWER_SUPPLY=y +CONFIG_QORIQ_THERMAL=y CONFIG_QUEUED_RWLOCKS=y CONFIG_QUEUED_SPINLOCKS=y CONFIG_REGMAP=y CONFIG_REGMAP_MMIO=y CONFIG_RODATA_FULL_DEFAULT_ENABLED=y +# CONFIG_RTC_DRV_FSL_FTM_ALARM is not set CONFIG_RTC_I2C_AND_SPI=y +CONFIG_SERIAL_8250_FSL=y CONFIG_SMC91X=y CONFIG_SPARSEMEM=y CONFIG_SPARSEMEM_EXTREME=y CONFIG_SPARSEMEM_VMEMMAP=y CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +# CONFIG_SPI_FSL_QUADSPI is not set # CONFIG_SPI_HISI_KUNPENG is not set # CONFIG_SPI_HISI_SFC_V3XX is not set CONFIG_SYNC_FILE=y CONFIG_SYSCTL_EXCEPTION_TRACE=y CONFIG_THREAD_INFO_IN_TASK=y +# CONFIG_UACCE is not set CONFIG_UNMAP_KERNEL_AT_EL0=y +CONFIG_USB_DWC3=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PLATFORM=y CONFIG_VEXPRESS_CONFIG=y CONFIG_VIDEOMODE_HELPERS=y CONFIG_VIRTIO_DMA_SHARED_BUFFER=y +# CONFIG_VIRTIO_IOMMU is not set CONFIG_VMAP_STACK=y -CONFIG_WATCHDOG_CORE=y CONFIG_ZONE_DMA32=y diff --git a/target/linux/armvirt/config-6.1 b/target/linux/armvirt/config-6.1 index 6cbc963913..6e6b64a313 100644 --- a/target/linux/armvirt/config-6.1 +++ b/target/linux/armvirt/config-6.1 @@ -65,17 +65,17 @@ CONFIG_ARM64_VA_BITS_39=y CONFIG_ARM_AMBA=y CONFIG_ARM_ARCH_TIMER=y CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y -# CONFIG_ARM_DMC620_PMU is not set CONFIG_ARM_GIC=y CONFIG_ARM_GIC_V2M=y CONFIG_ARM_GIC_V3=y CONFIG_ARM_GIC_V3_ITS=y CONFIG_ARM_GIC_V3_ITS_PCI=y CONFIG_ARM_PSCI_FW=y -# CONFIG_ARM_SMMU_V3_PMU is not set CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y CONFIG_BALLOON_COMPACTION=y CONFIG_BLK_DEV_LOOP=y +CONFIG_BLK_DEV_NVME=y +CONFIG_BLK_DEV_SD=y CONFIG_BLK_MQ_PCI=y CONFIG_BLK_MQ_VIRTIO=y CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y @@ -177,6 +177,7 @@ CONFIG_HZ_PERIODIC=y # CONFIG_I2C_AMD_MP2 is not set CONFIG_I2C_HID_ACPI=y # CONFIG_I2C_HISI is not set +# CONFIG_I2C_SLAVE_TESTUNIT is not set CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set CONFIG_INITRAMFS_SOURCE="" @@ -208,6 +209,8 @@ CONFIG_NET_FLOW_LIMIT=y CONFIG_NLS=y CONFIG_NR_CPUS=256 CONFIG_NVMEM=y +CONFIG_NVME_CORE=y +# CONFIG_NVME_MULTIPATH is not set CONFIG_OF=y CONFIG_OF_ADDRESS=y CONFIG_OF_EARLY_FLATTREE=y @@ -251,11 +254,19 @@ CONFIG_RWSEM_SPIN_ON_OWNER=y CONFIG_SCSI=y CONFIG_SCSI_COMMON=y CONFIG_SCSI_VIRTIO=y +CONFIG_SERIAL_8250=y +CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y +CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_FSL=y +CONFIG_SERIAL_8250_NR_UARTS=4 CONFIG_SERIAL_8250_PNP=y +CONFIG_SERIAL_8250_RUNTIME_UARTS=4 +CONFIG_SERIAL_8250_SHARE_IRQ=y CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_EARLYCON=y CONFIG_SERIAL_MCTRL_GPIO=y +CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SG_POOL=y CONFIG_SMP=y CONFIG_SOCK_RX_QUEUE_MAPPING=y @@ -282,9 +293,14 @@ CONFIG_TIMER_OF=y CONFIG_TIMER_PROBE=y CONFIG_TREE_RCU=y CONFIG_TREE_SRCU=y +# CONFIG_UACCE is not set CONFIG_UCS2_STRING=y CONFIG_UNMAP_KERNEL_AT_EL0=y +CONFIG_USB=y +CONFIG_USB_STORAGE=y CONFIG_USB_SUPPORT=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PCI=y CONFIG_VIRTIO=y CONFIG_VIRTIO_BALLOON=y CONFIG_VIRTIO_BLK=y @@ -296,5 +312,6 @@ CONFIG_VIRTIO_PCI=y CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_PCI_LIB=y CONFIG_VMAP_STACK=y +CONFIG_WATCHDOG_CORE=y CONFIG_XPS=y CONFIG_ZONE_DMA32=y diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index 50c993b522..2de26afb54 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -106,7 +106,8 @@ define Device/generic GRUB2_VARIANT := generic FILESYSTEMS := ext4 squashfs DEVICE_PACKAGES += kmod-amazon-ena kmod-e1000e kmod-vmxnet3 kmod-rtc-rx8025 \ - kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils + kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils kmod-wdt-sp805 \ + kmod-fsl-dpaa2-net kmod-sfp endef TARGET_DEVICES += generic diff --git a/target/linux/armvirt/modules.mk b/target/linux/armvirt/modules.mk new file mode 100644 index 0000000000..3ac3f6a27d --- /dev/null +++ b/target/linux/armvirt/modules.mk @@ -0,0 +1,96 @@ +define KernelPackage/acpi-mdio + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=ACPI MDIO support + DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy +kmod-mdio-devres + KCONFIG:=CONFIG_ACPI_MDIO + FILES:=$(LINUX_DIR)/drivers/net/mdio/acpi_mdio.ko + AUTOLOAD:=$(call AutoLoad,11,acpi_mdio) +endef + +define KernelPackage/acpi-mdio/description + Kernel driver for ACPI MDIO support +endef + +$(eval $(call KernelPackage,acpi-mdio)) + +define KernelPackage/fsl-pcs-lynx + SUBMENU=$(NETWORK_DEVICES_MENU) + DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy +kmod-of-mdio +kmod-phylink + TITLE:=NXP (Freescale) Lynx PCS + HIDDEN:=1 + KCONFIG:=CONFIG_PCS_LYNX + FILES=$(LINUX_DIR)/drivers/net/pcs/pcs-lynx.ko + AUTOLOAD=$(call AutoLoad,30,pcs-lynx) +endef + +$(eval $(call KernelPackage,fsl-pcs-lynx)) + +define KernelPackage/fsl-xgmac-mdio + SUBMENU=$(NETWORK_DEVICES_MENU) + DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy +kmod-of-mdio +kmod-acpi-mdio + TITLE:=NXP (Freescale) MDIO bus + KCONFIG:=CONFIG_FSL_XGMAC_MDIO + FILES=$(LINUX_DIR)/drivers/net/ethernet/freescale/xgmac_mdio.ko + AUTOLOAD=$(call AutoLoad,30,xgmac_mdio) +endef + +$(eval $(call KernelPackage,fsl-xgmac-mdio)) + +define KernelPackage/fsl-mc-dpio + SUBMENU:=$(OTHER_MENU) + TITLE:=NXP DPAA2 DPIO (Data Path IO) driver + HIDDEN:=1 + KCONFIG:=CONFIG_FSL_MC_BUS=y \ + CONFIG_FSL_MC_DPIO + FILES:=$(LINUX_DIR)/drivers/soc/fsl/dpio/fsl-mc-dpio.ko + AUTOLOAD=$(call AutoLoad,30,fsl-mc-dpio) +endef + +$(eval $(call KernelPackage,fsl-mc-dpio)) + +define KernelPackage/fsl-dpaa2-net + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=NXP DPAA2 Ethernet + DEPENDS:=@(TARGET_armvirt_64) +kmod-fsl-xgmac-mdio +kmod-phylink \ + +kmod-fsl-pcs-lynx +kmod-fsl-mc-dpio + KCONFIG:= \ + CONFIG_FSL_MC_UAPI_SUPPORT=y \ + CONFIG_FSL_DPAA2_ETH + FILES:= \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/dpaa2/fsl-dpaa2-eth.ko + AUTOLOAD=$(call AutoLoad,35,fsl-dpaa2-eth) +endef + +$(eval $(call KernelPackage,fsl-dpaa2-net)) + +define KernelPackage/fsl-dpaa2-console + SUBMENU:=$(OTHER_MENU) + TITLE:=NXP DPAA2 Debug console + DEPENDS:=@(TARGET_armvirt_64) + KCONFIG:=CONFIG_DPAA2_CONSOLE + FILES=$(LINUX_DIR)/drivers/soc/fsl/dpaa2-console.ko + AUTOLOAD=$(call AutoLoad,40,dpaa2-console) +endef + +define KernelPackage/fsl-dpaa2-console/description + Kernel modules for the NXP DPAA2 debug consoles + (Management Complex and AIOP). +endef + +$(eval $(call KernelPackage,fsl-dpaa2-console)) + +define KernelPackage/wdt-sp805 + SUBMENU:=$(OTHER_MENU) + TITLE:=ARM SP805 Watchdog + KCONFIG:=CONFIG_ARM_SP805_WATCHDOG + FILES=$(LINUX_DIR)/drivers/watchdog/sp805_wdt.ko + AUTOLOAD=$(call AutoLoad,50,sp805_wdt) +endef + +define KernelPackage/wdt-sp805/description + Support for the ARM SP805 wathchdog module. + This is present in the NXP Layerscape family, + HiSilicon HI3660 among others. +endef + +$(eval $(call KernelPackage,wdt-sp805)) From 61ec9a8154a58420e3c913c5e62a9e00b10f1936 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Fri, 2 Jun 2023 04:14:14 +0000 Subject: [PATCH 25/37] armvirt: add SFP support patches for NXP Layerscape DPAA2 platforms This series resolves a long term issue with Phylink and SFPs on DPAA2 hardware (LS1088,LS2088,LX2160) where a locking problem prevented the system from rebooting. Patches solving this issue existed prior to 6.2 but were not accepted upstream. See the original series on patchwork: https://patchwork.kernel.org/project/netdevbpf/cover/20221129141221.872653-1-vladimir.oltean@nxp.com/ And this thread on the Traverse forum: https://forum.traverse.com.au/t/reboot-command-regression-from-5-10-to-5-15-kernel/133/12 Signed-off-by: Mathew McBride --- ...a2-eth-don-t-use-ENOTSUPP-error-code.patch | 44 +++ ...e-dpaa2_mac_is_type_fixed-with-dpaa2.patch | 99 ++++++ ...sorb-phylink_start-call-into-dpaa2_m.patch | 88 +++++ ...move-defensive-check-in-dpaa2_mac_di.patch | 50 +++ ...sign-priv-mac-after-dpaa2_mac_connec.patch | 101 ++++++ ...-assign-port_priv-mac-after-dpaa2_ma.patch | 73 ++++ ...h-MAC-stringset-to-ethtool-S-even-if.patch | 111 ++++++ ...-replace-direct-MAC-access-with-dpaa.patch | 28 ++ ...nnect-to-MAC-before-requesting-the-e.patch | 93 +++++ ...rialize-changes-to-priv-mac-with-a-m.patch | 320 ++++++++++++++++++ ...h-serialize-changes-to-priv-mac-with.patch | 203 +++++++++++ ...c-move-rtnl_lock-only-around-phylink.patch | 113 +++++++ 12 files changed, 1323 insertions(+) create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0001-net-dpaa2-eth-don-t-use-ENOTSUPP-error-code.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0002-net-dpaa2-replace-dpaa2_mac_is_type_fixed-with-dpaa2.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0003-net-dpaa2-mac-absorb-phylink_start-call-into-dpaa2_m.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0004-net-dpaa2-mac-remove-defensive-check-in-dpaa2_mac_di.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0005-net-dpaa2-eth-assign-priv-mac-after-dpaa2_mac_connec.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0006-net-dpaa2-switch-assign-port_priv-mac-after-dpaa2_ma.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0007-net-dpaa2-publish-MAC-stringset-to-ethtool-S-even-if.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0008-net-dpaa2-switch-replace-direct-MAC-access-with-dpaa.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0009-net-dpaa2-eth-connect-to-MAC-before-requesting-the-e.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0010-net-dpaa2-eth-serialize-changes-to-priv-mac-with-a-m.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0011-net-dpaa2-switch-serialize-changes-to-priv-mac-with.patch create mode 100644 target/linux/armvirt/patches-6.1/701-v6.2-0012-net-dpaa2-mac-move-rtnl_lock-only-around-phylink.patch diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0001-net-dpaa2-eth-don-t-use-ENOTSUPP-error-code.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0001-net-dpaa2-eth-don-t-use-ENOTSUPP-error-code.patch new file mode 100644 index 0000000000..6238c0a903 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0001-net-dpaa2-eth-don-t-use-ENOTSUPP-error-code.patch @@ -0,0 +1,44 @@ +From f3763a0c1b07273218cbf5886bdf8df9df501111 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:10 +0200 +Subject: [PATCH 03/14] net: dpaa2-eth: don't use -ENOTSUPP error code + +dpaa2_eth_setup_dpni() is called from the probe path and +dpaa2_eth_set_link_ksettings() is propagated to user space. + +include/linux/errno.h says that ENOTSUPP is "Defined for the NFSv3 +protocol". Conventional wisdom has it to not use it in networking +drivers. Replace it with -EOPNOTSUPP. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Andrew Lunn +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c | 2 +- + drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +@@ -3613,7 +3613,7 @@ static int dpaa2_eth_setup_dpni(struct f + dev_err(dev, "DPNI version %u.%u not supported, need >= %u.%u\n", + priv->dpni_ver_major, priv->dpni_ver_minor, + DPNI_VER_MAJOR, DPNI_VER_MINOR); +- err = -ENOTSUPP; ++ err = -EOPNOTSUPP; + goto close; + } + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c +@@ -118,7 +118,7 @@ dpaa2_eth_set_link_ksettings(struct net_ + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + + if (!dpaa2_eth_is_type_phy(priv)) +- return -ENOTSUPP; ++ return -EOPNOTSUPP; + + return phylink_ethtool_ksettings_set(priv->mac->phylink, link_settings); + } diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0002-net-dpaa2-replace-dpaa2_mac_is_type_fixed-with-dpaa2.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0002-net-dpaa2-replace-dpaa2_mac_is_type_fixed-with-dpaa2.patch new file mode 100644 index 0000000000..501eaf42ff --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0002-net-dpaa2-replace-dpaa2_mac_is_type_fixed-with-dpaa2.patch @@ -0,0 +1,99 @@ +From 022a11062261dc4703da846d3bf4d194ef6bebf5 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:11 +0200 +Subject: [PATCH 04/14] net: dpaa2: replace dpaa2_mac_is_type_fixed() with + dpaa2_mac_is_type_phy() + +dpaa2_mac_is_type_fixed() is a header with no implementation and no +callers, which is referenced from the documentation though. It can be +deleted. + +On the other hand, it would be useful to reuse the code between +dpaa2_eth_is_type_phy() and dpaa2_switch_port_is_type_phy(). That common +code should be called dpaa2_mac_is_type_phy(), so let's create that. + +The removal and the addition are merged into the same patch because, +in fact, is_type_phy() is the logical opposite of is_type_fixed(). + +Signed-off-by: Vladimir Oltean +Reviewed-by: Andrew Lunn +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + .../ethernet/freescale/dpaa2/mac-phy-support.rst | 9 ++++++--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h | 7 +------ + drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h | 10 ++++++++-- + drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h | 7 +------ + 4 files changed, 16 insertions(+), 17 deletions(-) + +--- a/Documentation/networking/device_drivers/ethernet/freescale/dpaa2/mac-phy-support.rst ++++ b/Documentation/networking/device_drivers/ethernet/freescale/dpaa2/mac-phy-support.rst +@@ -181,10 +181,13 @@ when necessary using the below listed AP + - int dpaa2_mac_connect(struct dpaa2_mac *mac); + - void dpaa2_mac_disconnect(struct dpaa2_mac *mac); + +-A phylink integration is necessary only when the partner DPMAC is not of TYPE_FIXED. +-One can check for this condition using the below API:: ++A phylink integration is necessary only when the partner DPMAC is not of ++``TYPE_FIXED``. This means it is either of ``TYPE_PHY``, or of ++``TYPE_BACKPLANE`` (the difference being the two that in the ``TYPE_BACKPLANE`` ++mode, the MC firmware does not access the PCS registers). One can check for ++this condition using the following helper:: + +- - bool dpaa2_mac_is_type_fixed(struct fsl_mc_device *dpmac_dev,struct fsl_mc_io *mc_io); ++ - static inline bool dpaa2_mac_is_type_phy(struct dpaa2_mac *mac); + + Before connection to a MAC, the caller must allocate and populate the + dpaa2_mac structure with the associated net_device, a pointer to the MC portal +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h +@@ -733,12 +733,7 @@ static inline unsigned int dpaa2_eth_rx_ + + static inline bool dpaa2_eth_is_type_phy(struct dpaa2_eth_priv *priv) + { +- if (priv->mac && +- (priv->mac->attr.link_type == DPMAC_LINK_TYPE_PHY || +- priv->mac->attr.link_type == DPMAC_LINK_TYPE_BACKPLANE)) +- return true; +- +- return false; ++ return dpaa2_mac_is_type_phy(priv->mac); + } + + static inline bool dpaa2_eth_has_mac(struct dpaa2_eth_priv *priv) +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.h +@@ -30,8 +30,14 @@ struct dpaa2_mac { + struct phy *serdes_phy; + }; + +-bool dpaa2_mac_is_type_fixed(struct fsl_mc_device *dpmac_dev, +- struct fsl_mc_io *mc_io); ++static inline bool dpaa2_mac_is_type_phy(struct dpaa2_mac *mac) ++{ ++ if (!mac) ++ return false; ++ ++ return mac->attr.link_type == DPMAC_LINK_TYPE_PHY || ++ mac->attr.link_type == DPMAC_LINK_TYPE_BACKPLANE; ++} + + int dpaa2_mac_open(struct dpaa2_mac *mac); + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +@@ -230,12 +230,7 @@ static inline bool dpaa2_switch_supports + static inline bool + dpaa2_switch_port_is_type_phy(struct ethsw_port_priv *port_priv) + { +- if (port_priv->mac && +- (port_priv->mac->attr.link_type == DPMAC_LINK_TYPE_PHY || +- port_priv->mac->attr.link_type == DPMAC_LINK_TYPE_BACKPLANE)) +- return true; +- +- return false; ++ return dpaa2_mac_is_type_phy(port_priv->mac); + } + + static inline bool dpaa2_switch_port_has_mac(struct ethsw_port_priv *port_priv) diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0003-net-dpaa2-mac-absorb-phylink_start-call-into-dpaa2_m.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0003-net-dpaa2-mac-absorb-phylink_start-call-into-dpaa2_m.patch new file mode 100644 index 0000000000..e84195f3cd --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0003-net-dpaa2-mac-absorb-phylink_start-call-into-dpaa2_m.patch @@ -0,0 +1,88 @@ +From 97c07369ab8bf9895e05d4b468f18e6567263154 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:12 +0200 +Subject: [PATCH 05/14] net: dpaa2-mac: absorb phylink_start() call into + dpaa2_mac_start() + +The phylink handling is intended to be hidden inside the dpaa2_mac +object. Move the phylink_start() call into dpaa2_mac_start(), and +phylink_stop() into dpaa2_mac_stop(). + +Signed-off-by: Vladimir Oltean +Reviewed-by: Andrew Lunn +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c | 5 +---- + drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c | 8 ++++++++ + drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 5 +---- + 3 files changed, 10 insertions(+), 8 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +@@ -2082,10 +2082,8 @@ static int dpaa2_eth_open(struct net_dev + goto enable_err; + } + +- if (dpaa2_eth_is_type_phy(priv)) { ++ if (dpaa2_eth_is_type_phy(priv)) + dpaa2_mac_start(priv->mac); +- phylink_start(priv->mac->phylink); +- } + + return 0; + +@@ -2159,7 +2157,6 @@ static int dpaa2_eth_stop(struct net_dev + int retries = 10; + + if (dpaa2_eth_is_type_phy(priv)) { +- phylink_stop(priv->mac->phylink); + dpaa2_mac_stop(priv->mac); + } else { + netif_tx_stop_all_queues(net_dev); +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c +@@ -336,12 +336,20 @@ static void dpaa2_mac_set_supported_inte + + void dpaa2_mac_start(struct dpaa2_mac *mac) + { ++ ASSERT_RTNL(); ++ + if (mac->serdes_phy) + phy_power_on(mac->serdes_phy); ++ ++ phylink_start(mac->phylink); + } + + void dpaa2_mac_stop(struct dpaa2_mac *mac) + { ++ ASSERT_RTNL(); ++ ++ phylink_stop(mac->phylink); ++ + if (mac->serdes_phy) + phy_power_off(mac->serdes_phy); + } +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +@@ -703,10 +703,8 @@ static int dpaa2_switch_port_open(struct + + dpaa2_switch_enable_ctrl_if_napi(ethsw); + +- if (dpaa2_switch_port_is_type_phy(port_priv)) { ++ if (dpaa2_switch_port_is_type_phy(port_priv)) + dpaa2_mac_start(port_priv->mac); +- phylink_start(port_priv->mac->phylink); +- } + + return 0; + } +@@ -718,7 +716,6 @@ static int dpaa2_switch_port_stop(struct + int err; + + if (dpaa2_switch_port_is_type_phy(port_priv)) { +- phylink_stop(port_priv->mac->phylink); + dpaa2_mac_stop(port_priv->mac); + } else { + netif_tx_stop_all_queues(netdev); diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0004-net-dpaa2-mac-remove-defensive-check-in-dpaa2_mac_di.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0004-net-dpaa2-mac-remove-defensive-check-in-dpaa2_mac_di.patch new file mode 100644 index 0000000000..c3028357fe --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0004-net-dpaa2-mac-remove-defensive-check-in-dpaa2_mac_di.patch @@ -0,0 +1,50 @@ +From 095ef388f714d622aa503fcccf20dc4095b72762 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:13 +0200 +Subject: [PATCH 06/14] net: dpaa2-mac: remove defensive check in + dpaa2_mac_disconnect() + +dpaa2_mac_disconnect() will only be called with a NULL mac->phylink if +dpaa2_mac_connect() failed, or was never called. + +The callers are these: + +dpaa2_eth_disconnect_mac(): + + if (dpaa2_eth_is_type_phy(priv)) + dpaa2_mac_disconnect(priv->mac); + +dpaa2_switch_port_disconnect_mac(): + + if (dpaa2_switch_port_is_type_phy(port_priv)) + dpaa2_mac_disconnect(port_priv->mac); + +priv->mac can be NULL, but in that case, dpaa2_eth_is_type_phy() returns +false, and dpaa2_mac_disconnect() is never called. Similar for +dpaa2-switch. + +When priv->mac is non-NULL, it means that dpaa2_mac_connect() returned +zero (success), and therefore, priv->mac->phylink is also a valid +pointer. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Andrew Lunn +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c | 3 --- + 1 file changed, 3 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c +@@ -446,9 +446,6 @@ err_pcs_destroy: + + void dpaa2_mac_disconnect(struct dpaa2_mac *mac) + { +- if (!mac->phylink) +- return; +- + phylink_disconnect_phy(mac->phylink); + phylink_destroy(mac->phylink); + dpaa2_pcs_destroy(mac); diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0005-net-dpaa2-eth-assign-priv-mac-after-dpaa2_mac_connec.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0005-net-dpaa2-eth-assign-priv-mac-after-dpaa2_mac_connec.patch new file mode 100644 index 0000000000..8c7ed6c793 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0005-net-dpaa2-eth-assign-priv-mac-after-dpaa2_mac_connec.patch @@ -0,0 +1,101 @@ +From 06efc9b8a1360cad83cae6e71558e5458cc1fbf3 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:14 +0200 +Subject: [PATCH 07/14] net: dpaa2-eth: assign priv->mac after + dpaa2_mac_connect() call + +There are 2 requirements for correct code: + +- Any time the driver accesses the priv->mac pointer at runtime, it + either holds NULL to indicate a DPNI-DPNI connection (or unconnected + DPNI), or a struct dpaa2_mac whose phylink instance was fully + initialized (created and connected to the PHY). No changes are made to + priv->mac while it is being used. Currently, rtnl_lock() watches over + the call to dpaa2_eth_connect_mac(), so it serves the purpose of + serializing this with all readers of priv->mac. + +- dpaa2_mac_connect() should run unlocked, because inside it are 2 + phylink calls with incompatible locking requirements: phylink_create() + requires that the rtnl_mutex isn't held, and phylink_fwnode_phy_connect() + requires that the rtnl_mutex is held. The only way to solve those + contradictory requirements is to let dpaa2_mac_connect() take + rtnl_lock() when it needs to. + +To solve both requirements, we need to identify the writer side of the +priv->mac pointer, which can be wrapped in a mutex private to the driver +in a future patch. The dpaa2_mac_connect() cannot be part of the writer +side critical section, because of an AB/BA deadlock with rtnl_lock(). + +So the strategy needs to be that where we prepare the DPMAC by calling +dpaa2_mac_connect(), and only make priv->mac point to it once it's fully +prepared. This ensures that the writer side critical section has the +absolute minimum surface it can. + +The reverse strategy is adopted in the dpaa2_eth_disconnect_mac() code +path. This makes sure that priv->mac is NULL when we start tearing down +the DPMAC that we disconnected from, and concurrent code will simply not +see it. + +No locking changes in this patch (concurrent code is still blocked by +the rtnl_mutex). + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + .../net/ethernet/freescale/dpaa2/dpaa2-eth.c | 21 +++++++++++-------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +@@ -4443,9 +4443,8 @@ static int dpaa2_eth_connect_mac(struct + err = dpaa2_mac_open(mac); + if (err) + goto err_free_mac; +- priv->mac = mac; + +- if (dpaa2_eth_is_type_phy(priv)) { ++ if (dpaa2_mac_is_type_phy(mac)) { + err = dpaa2_mac_connect(mac); + if (err && err != -EPROBE_DEFER) + netdev_err(priv->net_dev, "Error connecting to the MAC endpoint: %pe", +@@ -4454,11 +4453,12 @@ static int dpaa2_eth_connect_mac(struct + goto err_close_mac; + } + ++ priv->mac = mac; ++ + return 0; + + err_close_mac: + dpaa2_mac_close(mac); +- priv->mac = NULL; + err_free_mac: + kfree(mac); + return err; +@@ -4466,15 +4466,18 @@ err_free_mac: + + static void dpaa2_eth_disconnect_mac(struct dpaa2_eth_priv *priv) + { +- if (dpaa2_eth_is_type_phy(priv)) +- dpaa2_mac_disconnect(priv->mac); ++ struct dpaa2_mac *mac = priv->mac; ++ ++ priv->mac = NULL; + +- if (!dpaa2_eth_has_mac(priv)) ++ if (!mac) + return; + +- dpaa2_mac_close(priv->mac); +- kfree(priv->mac); +- priv->mac = NULL; ++ if (dpaa2_mac_is_type_phy(mac)) ++ dpaa2_mac_disconnect(mac); ++ ++ dpaa2_mac_close(mac); ++ kfree(mac); + } + + static irqreturn_t dpni_irq0_handler_thread(int irq_num, void *arg) diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0006-net-dpaa2-switch-assign-port_priv-mac-after-dpaa2_ma.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0006-net-dpaa2-switch-assign-port_priv-mac-after-dpaa2_ma.patch new file mode 100644 index 0000000000..e63654984a --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0006-net-dpaa2-switch-assign-port_priv-mac-after-dpaa2_ma.patch @@ -0,0 +1,73 @@ +From a5e7f7e277bd4403c45c1c7922d56d0eb08dbc7c Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:15 +0200 +Subject: [PATCH 08/14] net: dpaa2-switch: assign port_priv->mac after + dpaa2_mac_connect() call + +The dpaa2-switch has the exact same locking requirements when connected +to a DPMAC, so it needs port_priv->mac to always point either to NULL, +or to a DPMAC with a fully initialized phylink instance. + +Make the same preparatory change in the dpaa2-switch driver as in the +dpaa2-eth one. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + .../ethernet/freescale/dpaa2/dpaa2-switch.c | 21 +++++++++++-------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +@@ -1450,9 +1450,8 @@ static int dpaa2_switch_port_connect_mac + err = dpaa2_mac_open(mac); + if (err) + goto err_free_mac; +- port_priv->mac = mac; + +- if (dpaa2_switch_port_is_type_phy(port_priv)) { ++ if (dpaa2_mac_is_type_phy(mac)) { + err = dpaa2_mac_connect(mac); + if (err) { + netdev_err(port_priv->netdev, +@@ -1462,11 +1461,12 @@ static int dpaa2_switch_port_connect_mac + } + } + ++ port_priv->mac = mac; ++ + return 0; + + err_close_mac: + dpaa2_mac_close(mac); +- port_priv->mac = NULL; + err_free_mac: + kfree(mac); + return err; +@@ -1474,15 +1474,18 @@ err_free_mac: + + static void dpaa2_switch_port_disconnect_mac(struct ethsw_port_priv *port_priv) + { +- if (dpaa2_switch_port_is_type_phy(port_priv)) +- dpaa2_mac_disconnect(port_priv->mac); ++ struct dpaa2_mac *mac = port_priv->mac; ++ ++ port_priv->mac = NULL; + +- if (!dpaa2_switch_port_has_mac(port_priv)) ++ if (!mac) + return; + +- dpaa2_mac_close(port_priv->mac); +- kfree(port_priv->mac); +- port_priv->mac = NULL; ++ if (dpaa2_mac_is_type_phy(mac)) ++ dpaa2_mac_disconnect(mac); ++ ++ dpaa2_mac_close(mac); ++ kfree(mac); + } + + static irqreturn_t dpaa2_switch_irq0_handler_thread(int irq_num, void *arg) diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0007-net-dpaa2-publish-MAC-stringset-to-ethtool-S-even-if.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0007-net-dpaa2-publish-MAC-stringset-to-ethtool-S-even-if.patch new file mode 100644 index 0000000000..c790ba1bd5 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0007-net-dpaa2-publish-MAC-stringset-to-ethtool-S-even-if.patch @@ -0,0 +1,111 @@ +From ce44b6ed9ee65efa9b3025552c513842eabcab88 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:16 +0200 +Subject: [PATCH 09/14] net: dpaa2: publish MAC stringset to ethtool -S even if + MAC is missing + +DPNIs and DPSW objects can connect and disconnect at runtime from DPMAC +objects on the same fsl-mc bus. The DPMAC object also holds "ethtool -S" +unstructured counters. Those counters are only shown for the entity +owning the netdev (DPNI, DPSW) if it's connected to a DPMAC. + +The ethtool stringset code path is split into multiple callbacks, but +currently, connecting and disconnecting the DPMAC takes the rtnl_lock(). +This blocks the entire ethtool code path from running, see +ethnl_default_doit() -> rtnl_lock() -> ops->prepare_data() -> +strset_prepare_data(). + +This is going to be a problem if we are going to no longer require +rtnl_lock() when connecting/disconnecting the DPMAC, because the DPMAC +could appear between ops->get_sset_count() and ops->get_strings(). +If it appears out of the blue, we will provide a stringset into an array +that was dimensioned thinking the DPMAC wouldn't be there => array +accessed out of bounds. + +There isn't really a good way to work around that, and I don't want to +put too much pressure on the ethtool framework by playing locking games. +Just make the DPMAC counters be always available. They'll be zeroes if +the DPNI or DPSW isn't connected to a DPMAC. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Andrew Lunn +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c | 12 +++--------- + .../ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c | 11 ++--------- + 2 files changed, 5 insertions(+), 18 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c +@@ -186,7 +186,6 @@ static int dpaa2_eth_set_pauseparam(stru + static void dpaa2_eth_get_strings(struct net_device *netdev, u32 stringset, + u8 *data) + { +- struct dpaa2_eth_priv *priv = netdev_priv(netdev); + u8 *p = data; + int i; + +@@ -200,22 +199,17 @@ static void dpaa2_eth_get_strings(struct + strscpy(p, dpaa2_ethtool_extras[i], ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } +- if (dpaa2_eth_has_mac(priv)) +- dpaa2_mac_get_strings(p); ++ dpaa2_mac_get_strings(p); + break; + } + } + + static int dpaa2_eth_get_sset_count(struct net_device *net_dev, int sset) + { +- int num_ss_stats = DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS; +- struct dpaa2_eth_priv *priv = netdev_priv(net_dev); +- + switch (sset) { + case ETH_SS_STATS: /* ethtool_get_stats(), ethtool_get_drvinfo() */ +- if (dpaa2_eth_has_mac(priv)) +- num_ss_stats += dpaa2_mac_get_sset_count(); +- return num_ss_stats; ++ return DPAA2_ETH_NUM_STATS + DPAA2_ETH_NUM_EXTRA_STATS + ++ dpaa2_mac_get_sset_count(); + default: + return -EOPNOTSUPP; + } +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c +@@ -145,14 +145,9 @@ dpaa2_switch_set_link_ksettings(struct n + static int + dpaa2_switch_ethtool_get_sset_count(struct net_device *netdev, int sset) + { +- struct ethsw_port_priv *port_priv = netdev_priv(netdev); +- int num_ss_stats = DPAA2_SWITCH_NUM_COUNTERS; +- + switch (sset) { + case ETH_SS_STATS: +- if (port_priv->mac) +- num_ss_stats += dpaa2_mac_get_sset_count(); +- return num_ss_stats; ++ return DPAA2_SWITCH_NUM_COUNTERS + dpaa2_mac_get_sset_count(); + default: + return -EOPNOTSUPP; + } +@@ -161,7 +156,6 @@ dpaa2_switch_ethtool_get_sset_count(stru + static void dpaa2_switch_ethtool_get_strings(struct net_device *netdev, + u32 stringset, u8 *data) + { +- struct ethsw_port_priv *port_priv = netdev_priv(netdev); + u8 *p = data; + int i; + +@@ -172,8 +166,7 @@ static void dpaa2_switch_ethtool_get_str + ETH_GSTRING_LEN); + p += ETH_GSTRING_LEN; + } +- if (port_priv->mac) +- dpaa2_mac_get_strings(p); ++ dpaa2_mac_get_strings(p); + break; + } + } diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0008-net-dpaa2-switch-replace-direct-MAC-access-with-dpaa.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0008-net-dpaa2-switch-replace-direct-MAC-access-with-dpaa.patch new file mode 100644 index 0000000000..0663bf6fb1 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0008-net-dpaa2-switch-replace-direct-MAC-access-with-dpaa.patch @@ -0,0 +1,28 @@ +From c838d9fd7e6ba9ddd6006bf0a296396266e9f121 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:17 +0200 +Subject: [PATCH 10/14] net: dpaa2-switch replace direct MAC access with + dpaa2_switch_port_has_mac() + +The helper function will gain a lockdep annotation in a future patch. +Make sure to benefit from it. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c +@@ -189,7 +189,7 @@ static void dpaa2_switch_ethtool_get_sta + dpaa2_switch_ethtool_counters[i].name, err); + } + +- if (port_priv->mac) ++ if (dpaa2_switch_port_has_mac(port_priv)) + dpaa2_mac_get_ethtool_stats(port_priv->mac, data + i); + } + diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0009-net-dpaa2-eth-connect-to-MAC-before-requesting-the-e.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0009-net-dpaa2-eth-connect-to-MAC-before-requesting-the-e.patch new file mode 100644 index 0000000000..37831f264b --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0009-net-dpaa2-eth-connect-to-MAC-before-requesting-the-e.patch @@ -0,0 +1,93 @@ +From e0ea63162cb5f1ca7f844d6ef5fc4079448ee2d5 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:18 +0200 +Subject: [PATCH 11/14] net: dpaa2-eth: connect to MAC before requesting the + "endpoint changed" IRQ + +dpaa2_eth_connect_mac() is called both from dpaa2_eth_probe() and from +dpni_irq0_handler_thread(). + +It could happen that the DPNI gets connected to a DPMAC on the fsl-mc +bus exactly during probe, as soon as the "endpoint change" interrupt is +requested in dpaa2_eth_setup_irqs(). This will cause the +dpni_irq0_handler_thread() to register a phylink instance for that DPMAC. + +Then, the probing function will also try to register a phylink instance +for the same DPMAC, operation which should fail (and this will fail the +probing of the driver). + +Reorder dpaa2_eth_setup_irqs() and dpaa2_eth_connect_mac(), such that +dpni_irq0_handler_thread() never races with the DPMAC-related portion of +the probing path. + +Also reorder dpaa2_eth_disconnect_mac() to be in the mirror position of +dpaa2_eth_connect_mac() in the teardown path. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + .../net/ethernet/freescale/dpaa2/dpaa2-eth.c | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +@@ -4710,6 +4710,10 @@ static int dpaa2_eth_probe(struct fsl_mc + } + #endif + ++ err = dpaa2_eth_connect_mac(priv); ++ if (err) ++ goto err_connect_mac; ++ + err = dpaa2_eth_setup_irqs(dpni_dev); + if (err) { + netdev_warn(net_dev, "Failed to set link interrupt, fall back to polling\n"); +@@ -4722,10 +4726,6 @@ static int dpaa2_eth_probe(struct fsl_mc + priv->do_link_poll = true; + } + +- err = dpaa2_eth_connect_mac(priv); +- if (err) +- goto err_connect_mac; +- + err = dpaa2_eth_dl_alloc(priv); + if (err) + goto err_dl_register; +@@ -4759,13 +4759,13 @@ err_dl_port_add: + err_dl_trap_register: + dpaa2_eth_dl_free(priv); + err_dl_register: +- dpaa2_eth_disconnect_mac(priv); +-err_connect_mac: + if (priv->do_link_poll) + kthread_stop(priv->poll_thread); + else + fsl_mc_free_irqs(dpni_dev); + err_poll_thread: ++ dpaa2_eth_disconnect_mac(priv); ++err_connect_mac: + dpaa2_eth_free_rings(priv); + err_alloc_rings: + err_csum: +@@ -4813,9 +4813,6 @@ static int dpaa2_eth_remove(struct fsl_m + #endif + + unregister_netdev(net_dev); +- rtnl_lock(); +- dpaa2_eth_disconnect_mac(priv); +- rtnl_unlock(); + + dpaa2_eth_dl_port_del(priv); + dpaa2_eth_dl_traps_unregister(priv); +@@ -4826,6 +4823,9 @@ static int dpaa2_eth_remove(struct fsl_m + else + fsl_mc_free_irqs(ls_dev); + ++ rtnl_lock(); ++ dpaa2_eth_disconnect_mac(priv); ++ rtnl_unlock(); + dpaa2_eth_free_rings(priv); + free_percpu(priv->fd); + free_percpu(priv->sgt_cache); diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0010-net-dpaa2-eth-serialize-changes-to-priv-mac-with-a-m.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0010-net-dpaa2-eth-serialize-changes-to-priv-mac-with-a-m.patch new file mode 100644 index 0000000000..89f58412d4 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0010-net-dpaa2-eth-serialize-changes-to-priv-mac-with-a-m.patch @@ -0,0 +1,320 @@ +From 5e448a17dfa2e95166534df7f677a3694ef6187d Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:19 +0200 +Subject: [PATCH 12/14] net: dpaa2-eth: serialize changes to priv->mac with a + mutex + +The dpaa2 architecture permits dynamic connections between objects on +the fsl-mc bus, specifically between a DPNI object (represented by a +struct net_device) and a DPMAC object (represented by a struct phylink). + +The DPNI driver is notified when those connections are created/broken +through the dpni_irq0_handler_thread() method. To ensure that ethtool +operations, as well as netdev up/down operations serialize with the +connection/disconnection of the DPNI with a DPMAC, +dpni_irq0_handler_thread() takes the rtnl_lock() to block those other +operations from taking place. + +There is code called by dpaa2_mac_connect() which wants to acquire the +rtnl_mutex once again, see phylink_create() -> phylink_register_sfp() -> +sfp_bus_add_upstream() -> rtnl_lock(). So the strategy doesn't quite +work out, even though it's fairly simple. + +Create a different strategy, where all code paths in the dpaa2-eth +driver access priv->mac only while they are holding priv->mac_lock. +The phylink instance is not created or connected to the PHY under the +priv->mac_lock, but only assigned to priv->mac then. This will eliminate +the reliance on the rtnl_mutex. + +Add lockdep annotations and put comments where holding the lock is not +necessary, and priv->mac can be dereferenced freely. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + .../net/ethernet/freescale/dpaa2/dpaa2-eth.c | 43 ++++++++++++-- + .../net/ethernet/freescale/dpaa2/dpaa2-eth.h | 6 ++ + .../ethernet/freescale/dpaa2/dpaa2-ethtool.c | 58 +++++++++++++++---- + 3 files changed, 91 insertions(+), 16 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +@@ -2020,8 +2020,11 @@ static int dpaa2_eth_link_state_update(s + + /* When we manage the MAC/PHY using phylink there is no need + * to manually update the netif_carrier. ++ * We can avoid locking because we are called from the "link changed" ++ * IRQ handler, which is the same as the "endpoint changed" IRQ handler ++ * (the writer to priv->mac), so we cannot race with it. + */ +- if (dpaa2_eth_is_type_phy(priv)) ++ if (dpaa2_mac_is_type_phy(priv->mac)) + goto out; + + /* Chech link state; speed / duplex changes are not treated yet */ +@@ -2060,6 +2063,8 @@ static int dpaa2_eth_open(struct net_dev + priv->dpbp_dev->obj_desc.id, priv->bpid); + } + ++ mutex_lock(&priv->mac_lock); ++ + if (!dpaa2_eth_is_type_phy(priv)) { + /* We'll only start the txqs when the link is actually ready; + * make sure we don't race against the link up notification, +@@ -2078,6 +2083,7 @@ static int dpaa2_eth_open(struct net_dev + + err = dpni_enable(priv->mc_io, 0, priv->mc_token); + if (err < 0) { ++ mutex_unlock(&priv->mac_lock); + netdev_err(net_dev, "dpni_enable() failed\n"); + goto enable_err; + } +@@ -2085,6 +2091,8 @@ static int dpaa2_eth_open(struct net_dev + if (dpaa2_eth_is_type_phy(priv)) + dpaa2_mac_start(priv->mac); + ++ mutex_unlock(&priv->mac_lock); ++ + return 0; + + enable_err: +@@ -2156,6 +2164,8 @@ static int dpaa2_eth_stop(struct net_dev + int dpni_enabled = 0; + int retries = 10; + ++ mutex_lock(&priv->mac_lock); ++ + if (dpaa2_eth_is_type_phy(priv)) { + dpaa2_mac_stop(priv->mac); + } else { +@@ -2163,6 +2173,8 @@ static int dpaa2_eth_stop(struct net_dev + netif_carrier_off(net_dev); + } + ++ mutex_unlock(&priv->mac_lock); ++ + /* On dpni_disable(), the MC firmware will: + * - stop MAC Rx and wait for all Rx frames to be enqueued to software + * - cut off WRIOP dequeues from egress FQs and wait until transmission +@@ -2488,12 +2500,20 @@ static int dpaa2_eth_ts_ioctl(struct net + static int dpaa2_eth_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) + { + struct dpaa2_eth_priv *priv = netdev_priv(dev); ++ int err; + + if (cmd == SIOCSHWTSTAMP) + return dpaa2_eth_ts_ioctl(dev, rq, cmd); + +- if (dpaa2_eth_is_type_phy(priv)) +- return phylink_mii_ioctl(priv->mac->phylink, rq, cmd); ++ mutex_lock(&priv->mac_lock); ++ ++ if (dpaa2_eth_is_type_phy(priv)) { ++ err = phylink_mii_ioctl(priv->mac->phylink, rq, cmd); ++ mutex_unlock(&priv->mac_lock); ++ return err; ++ } ++ ++ mutex_unlock(&priv->mac_lock); + + return -EOPNOTSUPP; + } +@@ -4453,7 +4473,9 @@ static int dpaa2_eth_connect_mac(struct + goto err_close_mac; + } + ++ mutex_lock(&priv->mac_lock); + priv->mac = mac; ++ mutex_unlock(&priv->mac_lock); + + return 0; + +@@ -4466,9 +4488,12 @@ err_free_mac: + + static void dpaa2_eth_disconnect_mac(struct dpaa2_eth_priv *priv) + { +- struct dpaa2_mac *mac = priv->mac; ++ struct dpaa2_mac *mac; + ++ mutex_lock(&priv->mac_lock); ++ mac = priv->mac; + priv->mac = NULL; ++ mutex_unlock(&priv->mac_lock); + + if (!mac) + return; +@@ -4487,6 +4512,7 @@ static irqreturn_t dpni_irq0_handler_thr + struct fsl_mc_device *dpni_dev = to_fsl_mc_device(dev); + struct net_device *net_dev = dev_get_drvdata(dev); + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ bool had_mac; + int err; + + err = dpni_get_irq_status(dpni_dev->mc_io, 0, dpni_dev->mc_handle, +@@ -4504,7 +4530,12 @@ static irqreturn_t dpni_irq0_handler_thr + dpaa2_eth_update_tx_fqids(priv); + + rtnl_lock(); +- if (dpaa2_eth_has_mac(priv)) ++ /* We can avoid locking because the "endpoint changed" IRQ ++ * handler is the only one who changes priv->mac at runtime, ++ * so we are not racing with anyone. ++ */ ++ had_mac = !!priv->mac; ++ if (had_mac) + dpaa2_eth_disconnect_mac(priv); + else + dpaa2_eth_connect_mac(priv); +@@ -4605,6 +4636,8 @@ static int dpaa2_eth_probe(struct fsl_mc + priv = netdev_priv(net_dev); + priv->net_dev = net_dev; + ++ mutex_init(&priv->mac_lock); ++ + priv->iommu_domain = iommu_get_domain_for_dev(dev); + + priv->tx_tstamp_type = HWTSTAMP_TX_OFF; +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.h +@@ -580,6 +580,8 @@ struct dpaa2_eth_priv { + #endif + + struct dpaa2_mac *mac; ++ /* Serializes changes to priv->mac */ ++ struct mutex mac_lock; + struct workqueue_struct *dpaa2_ptp_wq; + struct work_struct tx_onestep_tstamp; + struct sk_buff_head tx_skbs; +@@ -733,11 +735,15 @@ static inline unsigned int dpaa2_eth_rx_ + + static inline bool dpaa2_eth_is_type_phy(struct dpaa2_eth_priv *priv) + { ++ lockdep_assert_held(&priv->mac_lock); ++ + return dpaa2_mac_is_type_phy(priv->mac); + } + + static inline bool dpaa2_eth_has_mac(struct dpaa2_eth_priv *priv) + { ++ lockdep_assert_held(&priv->mac_lock); ++ + return priv->mac ? true : false; + } + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-ethtool.c +@@ -86,11 +86,16 @@ static void dpaa2_eth_get_drvinfo(struct + static int dpaa2_eth_nway_reset(struct net_device *net_dev) + { + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err = -EOPNOTSUPP; ++ ++ mutex_lock(&priv->mac_lock); + + if (dpaa2_eth_is_type_phy(priv)) +- return phylink_ethtool_nway_reset(priv->mac->phylink); ++ err = phylink_ethtool_nway_reset(priv->mac->phylink); ++ ++ mutex_unlock(&priv->mac_lock); + +- return -EOPNOTSUPP; ++ return err; + } + + static int +@@ -98,10 +103,18 @@ dpaa2_eth_get_link_ksettings(struct net_ + struct ethtool_link_ksettings *link_settings) + { + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err; + +- if (dpaa2_eth_is_type_phy(priv)) +- return phylink_ethtool_ksettings_get(priv->mac->phylink, +- link_settings); ++ mutex_lock(&priv->mac_lock); ++ ++ if (dpaa2_eth_is_type_phy(priv)) { ++ err = phylink_ethtool_ksettings_get(priv->mac->phylink, ++ link_settings); ++ mutex_unlock(&priv->mac_lock); ++ return err; ++ } ++ ++ mutex_unlock(&priv->mac_lock); + + link_settings->base.autoneg = AUTONEG_DISABLE; + if (!(priv->link_state.options & DPNI_LINK_OPT_HALF_DUPLEX)) +@@ -116,11 +129,17 @@ dpaa2_eth_set_link_ksettings(struct net_ + const struct ethtool_link_ksettings *link_settings) + { + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); ++ int err = -EOPNOTSUPP; + +- if (!dpaa2_eth_is_type_phy(priv)) +- return -EOPNOTSUPP; ++ mutex_lock(&priv->mac_lock); ++ ++ if (dpaa2_eth_is_type_phy(priv)) ++ err = phylink_ethtool_ksettings_set(priv->mac->phylink, ++ link_settings); + +- return phylink_ethtool_ksettings_set(priv->mac->phylink, link_settings); ++ mutex_unlock(&priv->mac_lock); ++ ++ return err; + } + + static void dpaa2_eth_get_pauseparam(struct net_device *net_dev, +@@ -129,11 +148,16 @@ static void dpaa2_eth_get_pauseparam(str + struct dpaa2_eth_priv *priv = netdev_priv(net_dev); + u64 link_options = priv->link_state.options; + ++ mutex_lock(&priv->mac_lock); ++ + if (dpaa2_eth_is_type_phy(priv)) { + phylink_ethtool_get_pauseparam(priv->mac->phylink, pause); ++ mutex_unlock(&priv->mac_lock); + return; + } + ++ mutex_unlock(&priv->mac_lock); ++ + pause->rx_pause = dpaa2_eth_rx_pause_enabled(link_options); + pause->tx_pause = dpaa2_eth_tx_pause_enabled(link_options); + pause->autoneg = AUTONEG_DISABLE; +@@ -152,9 +176,17 @@ static int dpaa2_eth_set_pauseparam(stru + return -EOPNOTSUPP; + } + +- if (dpaa2_eth_is_type_phy(priv)) +- return phylink_ethtool_set_pauseparam(priv->mac->phylink, +- pause); ++ mutex_lock(&priv->mac_lock); ++ ++ if (dpaa2_eth_is_type_phy(priv)) { ++ err = phylink_ethtool_set_pauseparam(priv->mac->phylink, ++ pause); ++ mutex_unlock(&priv->mac_lock); ++ return err; ++ } ++ ++ mutex_unlock(&priv->mac_lock); ++ + if (pause->autoneg) + return -EOPNOTSUPP; + +@@ -309,8 +341,12 @@ static void dpaa2_eth_get_ethtool_stats( + } + *(data + i++) = buf_cnt; + ++ mutex_lock(&priv->mac_lock); ++ + if (dpaa2_eth_has_mac(priv)) + dpaa2_mac_get_ethtool_stats(priv->mac, data + i); ++ ++ mutex_unlock(&priv->mac_lock); + } + + static int dpaa2_eth_prep_eth_rule(struct ethhdr *eth_value, struct ethhdr *eth_mask, diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0011-net-dpaa2-switch-serialize-changes-to-priv-mac-with.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0011-net-dpaa2-switch-serialize-changes-to-priv-mac-with.patch new file mode 100644 index 0000000000..7ea446516b --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0011-net-dpaa2-switch-serialize-changes-to-priv-mac-with.patch @@ -0,0 +1,203 @@ +From 80d12452a5f160c39d63efc1be07df36f9d07133 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:20 +0200 +Subject: [PATCH 13/14] net: dpaa2-switch: serialize changes to priv->mac with + a mutex + +The dpaa2-switch driver uses a DPMAC in the same way as the dpaa2-eth +driver, so we need to duplicate the locking solution established by the +previous change to the switch driver as well. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + .../freescale/dpaa2/dpaa2-switch-ethtool.c | 32 +++++++++++++++---- + .../ethernet/freescale/dpaa2/dpaa2-switch.c | 31 ++++++++++++++++-- + .../ethernet/freescale/dpaa2/dpaa2-switch.h | 2 ++ + 3 files changed, 55 insertions(+), 10 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch-ethtool.c +@@ -60,11 +60,18 @@ dpaa2_switch_get_link_ksettings(struct n + { + struct ethsw_port_priv *port_priv = netdev_priv(netdev); + struct dpsw_link_state state = {0}; +- int err = 0; ++ int err; + +- if (dpaa2_switch_port_is_type_phy(port_priv)) +- return phylink_ethtool_ksettings_get(port_priv->mac->phylink, +- link_ksettings); ++ mutex_lock(&port_priv->mac_lock); ++ ++ if (dpaa2_switch_port_is_type_phy(port_priv)) { ++ err = phylink_ethtool_ksettings_get(port_priv->mac->phylink, ++ link_ksettings); ++ mutex_unlock(&port_priv->mac_lock); ++ return err; ++ } ++ ++ mutex_unlock(&port_priv->mac_lock); + + err = dpsw_if_get_link_state(port_priv->ethsw_data->mc_io, 0, + port_priv->ethsw_data->dpsw_handle, +@@ -99,9 +106,16 @@ dpaa2_switch_set_link_ksettings(struct n + bool if_running; + int err = 0, ret; + +- if (dpaa2_switch_port_is_type_phy(port_priv)) +- return phylink_ethtool_ksettings_set(port_priv->mac->phylink, +- link_ksettings); ++ mutex_lock(&port_priv->mac_lock); ++ ++ if (dpaa2_switch_port_is_type_phy(port_priv)) { ++ err = phylink_ethtool_ksettings_set(port_priv->mac->phylink, ++ link_ksettings); ++ mutex_unlock(&port_priv->mac_lock); ++ return err; ++ } ++ ++ mutex_unlock(&port_priv->mac_lock); + + /* Interface needs to be down to change link settings */ + if_running = netif_running(netdev); +@@ -189,8 +203,12 @@ static void dpaa2_switch_ethtool_get_sta + dpaa2_switch_ethtool_counters[i].name, err); + } + ++ mutex_lock(&port_priv->mac_lock); ++ + if (dpaa2_switch_port_has_mac(port_priv)) + dpaa2_mac_get_ethtool_stats(port_priv->mac, data + i); ++ ++ mutex_unlock(&port_priv->mac_lock); + } + + const struct ethtool_ops dpaa2_switch_port_ethtool_ops = { +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +@@ -603,8 +603,11 @@ static int dpaa2_switch_port_link_state_ + + /* When we manage the MAC/PHY using phylink there is no need + * to manually update the netif_carrier. ++ * We can avoid locking because we are called from the "link changed" ++ * IRQ handler, which is the same as the "endpoint changed" IRQ handler ++ * (the writer to port_priv->mac), so we cannot race with it. + */ +- if (dpaa2_switch_port_is_type_phy(port_priv)) ++ if (dpaa2_mac_is_type_phy(port_priv->mac)) + return 0; + + /* Interrupts are received even though no one issued an 'ifconfig up' +@@ -684,6 +687,8 @@ static int dpaa2_switch_port_open(struct + struct ethsw_core *ethsw = port_priv->ethsw_data; + int err; + ++ mutex_lock(&port_priv->mac_lock); ++ + if (!dpaa2_switch_port_is_type_phy(port_priv)) { + /* Explicitly set carrier off, otherwise + * netif_carrier_ok() will return true and cause 'ip link show' +@@ -697,6 +702,7 @@ static int dpaa2_switch_port_open(struct + port_priv->ethsw_data->dpsw_handle, + port_priv->idx); + if (err) { ++ mutex_unlock(&port_priv->mac_lock); + netdev_err(netdev, "dpsw_if_enable err %d\n", err); + return err; + } +@@ -706,6 +712,8 @@ static int dpaa2_switch_port_open(struct + if (dpaa2_switch_port_is_type_phy(port_priv)) + dpaa2_mac_start(port_priv->mac); + ++ mutex_unlock(&port_priv->mac_lock); ++ + return 0; + } + +@@ -715,6 +723,8 @@ static int dpaa2_switch_port_stop(struct + struct ethsw_core *ethsw = port_priv->ethsw_data; + int err; + ++ mutex_lock(&port_priv->mac_lock); ++ + if (dpaa2_switch_port_is_type_phy(port_priv)) { + dpaa2_mac_stop(port_priv->mac); + } else { +@@ -722,6 +732,8 @@ static int dpaa2_switch_port_stop(struct + netif_carrier_off(netdev); + } + ++ mutex_unlock(&port_priv->mac_lock); ++ + err = dpsw_if_disable(port_priv->ethsw_data->mc_io, 0, + port_priv->ethsw_data->dpsw_handle, + port_priv->idx); +@@ -1461,7 +1473,9 @@ static int dpaa2_switch_port_connect_mac + } + } + ++ mutex_lock(&port_priv->mac_lock); + port_priv->mac = mac; ++ mutex_unlock(&port_priv->mac_lock); + + return 0; + +@@ -1474,9 +1488,12 @@ err_free_mac: + + static void dpaa2_switch_port_disconnect_mac(struct ethsw_port_priv *port_priv) + { +- struct dpaa2_mac *mac = port_priv->mac; ++ struct dpaa2_mac *mac; + ++ mutex_lock(&port_priv->mac_lock); ++ mac = port_priv->mac; + port_priv->mac = NULL; ++ mutex_unlock(&port_priv->mac_lock); + + if (!mac) + return; +@@ -1495,6 +1512,7 @@ static irqreturn_t dpaa2_switch_irq0_han + struct ethsw_port_priv *port_priv; + u32 status = ~0; + int err, if_id; ++ bool had_mac; + + err = dpsw_get_irq_status(ethsw->mc_io, 0, ethsw->dpsw_handle, + DPSW_IRQ_INDEX_IF, &status); +@@ -1513,7 +1531,12 @@ static irqreturn_t dpaa2_switch_irq0_han + + if (status & DPSW_IRQ_EVENT_ENDPOINT_CHANGED) { + rtnl_lock(); +- if (dpaa2_switch_port_has_mac(port_priv)) ++ /* We can avoid locking because the "endpoint changed" IRQ ++ * handler is the only one who changes priv->mac at runtime, ++ * so we are not racing with anyone. ++ */ ++ had_mac = !!port_priv->mac; ++ if (had_mac) + dpaa2_switch_port_disconnect_mac(port_priv); + else + dpaa2_switch_port_connect_mac(port_priv); +@@ -3256,6 +3279,8 @@ static int dpaa2_switch_probe_port(struc + port_priv->netdev = port_netdev; + port_priv->ethsw_data = ethsw; + ++ mutex_init(&port_priv->mac_lock); ++ + port_priv->idx = port_idx; + port_priv->stp_state = BR_STATE_FORWARDING; + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.h +@@ -161,6 +161,8 @@ struct ethsw_port_priv { + + struct dpaa2_switch_filter_block *filter_block; + struct dpaa2_mac *mac; ++ /* Protects against changes to port_priv->mac */ ++ struct mutex mac_lock; + }; + + /* Switch data */ diff --git a/target/linux/armvirt/patches-6.1/701-v6.2-0012-net-dpaa2-mac-move-rtnl_lock-only-around-phylink.patch b/target/linux/armvirt/patches-6.1/701-v6.2-0012-net-dpaa2-mac-move-rtnl_lock-only-around-phylink.patch new file mode 100644 index 0000000000..976c2a0335 --- /dev/null +++ b/target/linux/armvirt/patches-6.1/701-v6.2-0012-net-dpaa2-mac-move-rtnl_lock-only-around-phylink.patch @@ -0,0 +1,113 @@ +From 4ea2faf5bb13d9ba9f07e996d495c4cbe34a4236 Mon Sep 17 00:00:00 2001 +From: Vladimir Oltean +Date: Tue, 29 Nov 2022 16:12:21 +0200 +Subject: [PATCH 14/14] net: dpaa2-mac: move rtnl_lock() only around + phylink_{,dis}connect_phy() + +After the introduction of a private mac_lock that serializes access to +priv->mac (and port_priv->mac in the switch), the only remaining purpose +of rtnl_lock() is to satisfy the locking requirements of +phylink_fwnode_phy_connect() and phylink_disconnect_phy(). + +But the functions these live in, dpaa2_mac_connect() and +dpaa2_mac_disconnect(), have contradictory locking requirements. +While phylink_fwnode_phy_connect() wants rtnl_lock() to be held, +phylink_create() wants it to not be held. + +Move the rtnl_lock() from top-level (in the dpaa2-eth and dpaa2-switch +drivers) to only surround the phylink calls that require it, in the +dpaa2-mac library code. + +This is possible because dpaa2_mac_connect() and dpaa2_mac_disconnect() +run unlocked, and there isn't any danger of an AB/BA deadlock between +the rtnl_mutex and other private locks. + +Signed-off-by: Vladimir Oltean +Reviewed-by: Ioana Ciornei +Tested-by: Ioana Ciornei +Signed-off-by: Paolo Abeni +--- + drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c | 4 ---- + drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c | 5 +++++ + drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c | 4 ---- + 3 files changed, 5 insertions(+), 8 deletions(-) + +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-eth.c +@@ -4529,7 +4529,6 @@ static irqreturn_t dpni_irq0_handler_thr + dpaa2_eth_set_mac_addr(netdev_priv(net_dev)); + dpaa2_eth_update_tx_fqids(priv); + +- rtnl_lock(); + /* We can avoid locking because the "endpoint changed" IRQ + * handler is the only one who changes priv->mac at runtime, + * so we are not racing with anyone. +@@ -4539,7 +4538,6 @@ static irqreturn_t dpni_irq0_handler_thr + dpaa2_eth_disconnect_mac(priv); + else + dpaa2_eth_connect_mac(priv); +- rtnl_unlock(); + } + + return IRQ_HANDLED; +@@ -4856,9 +4854,7 @@ static int dpaa2_eth_remove(struct fsl_m + else + fsl_mc_free_irqs(ls_dev); + +- rtnl_lock(); + dpaa2_eth_disconnect_mac(priv); +- rtnl_unlock(); + dpaa2_eth_free_rings(priv); + free_percpu(priv->fd); + free_percpu(priv->sgt_cache); +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-mac.c +@@ -428,7 +428,9 @@ int dpaa2_mac_connect(struct dpaa2_mac * + } + mac->phylink = phylink; + ++ rtnl_lock(); + err = phylink_fwnode_phy_connect(mac->phylink, dpmac_node, 0); ++ rtnl_unlock(); + if (err) { + netdev_err(net_dev, "phylink_fwnode_phy_connect() = %d\n", err); + goto err_phylink_destroy; +@@ -446,7 +448,10 @@ err_pcs_destroy: + + void dpaa2_mac_disconnect(struct dpaa2_mac *mac) + { ++ rtnl_lock(); + phylink_disconnect_phy(mac->phylink); ++ rtnl_unlock(); ++ + phylink_destroy(mac->phylink); + dpaa2_pcs_destroy(mac); + of_phy_put(mac->serdes_phy); +--- a/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c ++++ b/drivers/net/ethernet/freescale/dpaa2/dpaa2-switch.c +@@ -1530,7 +1530,6 @@ static irqreturn_t dpaa2_switch_irq0_han + } + + if (status & DPSW_IRQ_EVENT_ENDPOINT_CHANGED) { +- rtnl_lock(); + /* We can avoid locking because the "endpoint changed" IRQ + * handler is the only one who changes priv->mac at runtime, + * so we are not racing with anyone. +@@ -1540,7 +1539,6 @@ static irqreturn_t dpaa2_switch_irq0_han + dpaa2_switch_port_disconnect_mac(port_priv); + else + dpaa2_switch_port_connect_mac(port_priv); +- rtnl_unlock(); + } + + out: +@@ -2958,9 +2956,7 @@ static void dpaa2_switch_remove_port(str + { + struct ethsw_port_priv *port_priv = ethsw->ports[port_idx]; + +- rtnl_lock(); + dpaa2_switch_port_disconnect_mac(port_priv); +- rtnl_unlock(); + free_netdev(port_priv->netdev); + ethsw->ports[port_idx] = NULL; + } From c3151b6f04579a937b7cb166bbeff0d0ee539946 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Fri, 15 Jul 2022 02:38:02 +0000 Subject: [PATCH 26/37] armvirt: 64: add support for other SystemReady-compatible vendors These changes are to support other vendors that have SystemReady/EFI support, including: * Marvell Armada ** (This is speculative as I don't have a machine of my own to test) * Amazon Graviton (tested bare-metal and virtualized instances) * VMware (Fusion for ARM Mac preview) * NXP/Freescale (Layerscape series not already selected) * HiSilicon * Allwinner/sunxi * Rockchip (untested, options taken from arm64 defconfig) To give an idea of the hardware certified for SystemReady, see https://www.arm.com/architecture/system-architectures/systemready-certification-program/ir and https://www.arm.com/architecture/system-architectures/systemready-certification-program/es Other vendors that _should_ work include Marvell Octeon 10 and Ampere. I understand these systems should work "out of the box" in ACPI mode but may require other drivers (e.g PCIe NICs and storage controllers). Signed-off-by: Mathew McBride --- target/linux/armvirt/64/config-6.1 | 232 +++++++++++++++++++++++++++- target/linux/armvirt/config-6.1 | 1 + target/linux/armvirt/image/Makefile | 5 +- target/linux/armvirt/modules.mk | 84 ++++++++++ 4 files changed, 319 insertions(+), 3 deletions(-) diff --git a/target/linux/armvirt/64/config-6.1 b/target/linux/armvirt/64/config-6.1 index 6bb43c22ca..343a333542 100644 --- a/target/linux/armvirt/64/config-6.1 +++ b/target/linux/armvirt/64/config-6.1 @@ -1,15 +1,25 @@ CONFIG_64BIT=y +CONFIG_ACPI_PCC=y +CONFIG_ARCH_HISI=y +CONFIG_ARCH_INTEL_SOCFPGA=y CONFIG_ARCH_LAYERSCAPE=y CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y CONFIG_ARCH_MMAP_RND_BITS=18 CONFIG_ARCH_MMAP_RND_BITS_MAX=24 CONFIG_ARCH_MMAP_RND_BITS_MIN=18 CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=11 +CONFIG_ARCH_MVEBU=y CONFIG_ARCH_NXP=y CONFIG_ARCH_PROC_KCORE_TEXT=y +CONFIG_ARCH_ROCKCHIP=y CONFIG_ARCH_STACKWALK=y +CONFIG_ARCH_SUNXI=y +CONFIG_ARCH_SYNQUACER=y +CONFIG_ARCH_THUNDER=y +CONFIG_ARCH_THUNDER2=y CONFIG_ARCH_VEXPRESS=y CONFIG_ARCH_WANTS_NO_INSTR=y +CONFIG_ARCH_ZYNQMP=y CONFIG_ARM64=y CONFIG_ARM64_4K_PAGES=y CONFIG_ARM64_CNP=y @@ -39,8 +49,12 @@ CONFIG_ARM64_VA_BITS_48=y CONFIG_ARM64_WORKAROUND_CLEAN_CACHE=y CONFIG_ARM64_WORKAROUND_REPEAT_TLBI=y CONFIG_ARM64_WORKAROUND_SPECULATIVE_AT=y +# CONFIG_ARMADA_37XX_RWTM_MBOX is not set +CONFIG_ARMADA_37XX_WATCHDOG=y +CONFIG_ARMADA_THERMAL=y CONFIG_ARM_ARCH_TIMER_OOL_WORKAROUND=y # CONFIG_ARM_DMC620_PMU is not set +# CONFIG_ARM_MHU_V2 is not set CONFIG_ARM_SBSA_WATCHDOG=y CONFIG_ARM_SMC_WATCHDOG=y CONFIG_ARM_SMMU=y @@ -51,16 +65,43 @@ CONFIG_ARM_SMMU_V3=y # CONFIG_ARM_SMMU_V3_SVA is not set CONFIG_ATOMIC64_SELFTEST=y CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y +# CONFIG_AXI_DMAC is not set CONFIG_BACKLIGHT_CLASS_DEVICE=y CONFIG_BLK_PM=y CONFIG_CAVIUM_TX2_ERRATUM_219=y CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y +CONFIG_CLK_INTEL_SOCFPGA=y +CONFIG_CLK_INTEL_SOCFPGA64=y CONFIG_CLK_LS1028A_PLLDIG=y +CONFIG_CLK_PX30=y CONFIG_CLK_QORIQ=y +CONFIG_CLK_RK3308=y +CONFIG_CLK_RK3328=y +CONFIG_CLK_RK3368=y +CONFIG_CLK_RK3399=y +CONFIG_CLK_RK3568=y CONFIG_CLK_SP810=y +CONFIG_CLK_SUNXI=y +CONFIG_CLK_SUNXI_CLOCKS=y +# CONFIG_CLK_SUNXI_PRCM_SUN6I is not set +# CONFIG_CLK_SUNXI_PRCM_SUN8I is not set +# CONFIG_CLK_SUNXI_PRCM_SUN9I is not set CONFIG_CLK_VEXPRESS_OSC=y # CONFIG_COMMON_CLK_FSL_FLEXSPI is not set # CONFIG_COMMON_CLK_FSL_SAI is not set +CONFIG_COMMON_CLK_HI3516CV300=y +CONFIG_COMMON_CLK_HI3519=y +CONFIG_COMMON_CLK_HI3559A=y +CONFIG_COMMON_CLK_HI3660=y +CONFIG_COMMON_CLK_HI3670=y +CONFIG_COMMON_CLK_HI3798CV200=y +CONFIG_COMMON_CLK_HI6220=y +CONFIG_COMMON_CLK_HI655X=y +CONFIG_COMMON_CLK_ROCKCHIP=y +CONFIG_COMMON_CLK_SCPI=y +CONFIG_COMMON_CLK_ZYNQMP=y +CONFIG_COMMON_RESET_HI3660=y +CONFIG_COMMON_RESET_HI6220=y # CONFIG_COMPAT_32BIT_TIME is not set CONFIG_CPU_IDLE=y CONFIG_CPU_IDLE_GOV_MENU=y @@ -77,7 +118,15 @@ CONFIG_CRYPTO_BLAKE2S=y CONFIG_CRYPTO_CHACHA20=y CONFIG_CRYPTO_CHACHA20_NEON=y CONFIG_CRYPTO_CRYPTD=y +# CONFIG_CRYPTO_DEV_ALLWINNER is not set # CONFIG_CRYPTO_DEV_FSL_DPAA2_CAAM is not set +# CONFIG_CRYPTO_DEV_HISI_HPRE is not set +# CONFIG_CRYPTO_DEV_HISI_SEC2 is not set +# CONFIG_CRYPTO_DEV_HISI_TRNG is not set +# CONFIG_CRYPTO_DEV_OCTEONTX2_CPT is not set +# CONFIG_CRYPTO_DEV_ROCKCHIP is not set +# CONFIG_CRYPTO_DEV_ZYNQMP_AES is not set +# CONFIG_CRYPTO_DEV_ZYNQMP_SHA3 is not set CONFIG_CRYPTO_GHASH_ARM64_CE=y CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y CONFIG_CRYPTO_LIB_CHACHA_GENERIC=y @@ -92,6 +141,7 @@ CONFIG_CRYPTO_SIMD=y # CONFIG_CRYPTO_SM4_ARM64_NEON_BLK is not set CONFIG_DMA_DIRECT_REMAP=y CONFIG_DMA_SHARED_BUFFER=y +CONFIG_DMA_SUN6I=y CONFIG_DRM=y CONFIG_DRM_BOCHS=y CONFIG_DRM_BRIDGE=y @@ -101,10 +151,14 @@ CONFIG_DRM_PANEL=y CONFIG_DRM_PANEL_BRIDGE=y CONFIG_DRM_PANEL_ORIENTATION_QUIRKS=y CONFIG_DRM_QXL=y +# CONFIG_DRM_ROCKCHIP is not set CONFIG_DRM_TTM=y CONFIG_DRM_TTM_HELPER=y CONFIG_DRM_VIRTIO_GPU=y CONFIG_DRM_VRAM_HELPER=y +# CONFIG_DWMAC_SUN8I is not set +# CONFIG_DWMAC_SUNXI is not set +CONFIG_DW_WATCHDOG=y CONFIG_FB=y CONFIG_FB_ARMCLCD=y CONFIG_FB_CFB_COPYAREA=y @@ -112,12 +166,12 @@ CONFIG_FB_CFB_FILLRECT=y CONFIG_FB_CFB_IMAGEBLIT=y CONFIG_FB_CMDLINE=y CONFIG_FB_MODE_HELPERS=y +# CONFIG_FB_XILINX is not set CONFIG_FRAME_POINTER=y CONFIG_FRAME_WARN=2048 # CONFIG_FSL_DPAA is not set # CONFIG_FSL_DPAA2_QDMA is not set CONFIG_FSL_ERRATUM_A008585=y -# CONFIG_FSL_FMAN is not set # CONFIG_FSL_IMX8_DDR_PMU is not set # CONFIG_FSL_PQ_MDIO is not set CONFIG_FUJITSU_ERRATUM_010001=y @@ -128,19 +182,37 @@ CONFIG_GENERIC_FIND_FIRST_BIT=y CONFIG_GPIO_GENERIC=y CONFIG_GPIO_GENERIC_PLATFORM=y CONFIG_GPIO_MPC8XXX=y +CONFIG_GPIO_ROCKCHIP=y +CONFIG_GPIO_THUNDERX=y +CONFIG_GPIO_XLP=y +CONFIG_GPIO_ZYNQ=y +CONFIG_GPIO_ZYNQMP_MODEPIN=y CONFIG_HDMI=y -# CONFIG_HISI_PMU is not set +CONFIG_HI3660_MBOX=y +CONFIG_HI6220_MBOX=y +CONFIG_HISILICON_LPC=y +CONFIG_HISI_PMU=y +CONFIG_HISI_THERMAL=y CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_ARM_SMCCC_TRNG=y +# CONFIG_HW_RANDOM_HISI is not set CONFIG_HW_RANDOM_VIRTIO=y CONFIG_I2C=y CONFIG_I2C_ALGOBIT=y +CONFIG_I2C_ALTERA=y CONFIG_I2C_BOARDINFO=y +# CONFIG_I2C_HIX5HD2 is not set CONFIG_I2C_IMX=y # CONFIG_I2C_SLAVE_TESTUNIT is not set +CONFIG_I2C_SYNQUACER=y +CONFIG_I2C_THUNDERX=y +# CONFIG_I2C_XLP9XX is not set CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 # CONFIG_IMX2_WDT is not set +# CONFIG_INPUT_HISI_POWERKEY is not set # CONFIG_INPUT_IBM_PANEL is not set +# CONFIG_INTEL_STRATIX10_RSU is not set +# CONFIG_INTEL_STRATIX10_SERVICE is not set # CONFIG_IOMMU_DEBUGFS is not set # CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set CONFIG_IOMMU_DEFAULT_DMA_STRICT=y @@ -149,36 +221,131 @@ CONFIG_IOMMU_DEFAULT_PASSTHROUGH=y # CONFIG_IOMMU_IO_PGTABLE_DART is not set # CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST is not set CONFIG_IOMMU_SUPPORT=y +# CONFIG_K3_DMA is not set CONFIG_KCMP=y +# CONFIG_KEYBOARD_SUN4I_LRADC is not set CONFIG_LCD_CLASS_DEVICE=m # CONFIG_LCD_PLATFORM is not set +# CONFIG_MAILBOX_TEST is not set +CONFIG_MARVELL_10G_PHY=y +# CONFIG_MARVELL_CN10K_DDR_PMU is not set +# CONFIG_MARVELL_CN10K_TAD_PMU is not set +CONFIG_MDIO_SUN4I=y +# CONFIG_MFD_ALTERA_A10SR is not set +CONFIG_MFD_ALTERA_SYSMGR=y +# CONFIG_MFD_AXP20X_RSB is not set CONFIG_MFD_CORE=y +CONFIG_MFD_HI655X_PMIC=y +# CONFIG_MFD_KHADAS_MCU is not set +CONFIG_MFD_SUN4I_GPADC=y +# CONFIG_MFD_SUN6I_PRCM is not set CONFIG_MFD_SYSCON=y CONFIG_MFD_VEXPRESS_SYSREG=y CONFIG_MMC=y CONFIG_MMC_ARMMMCI=y +CONFIG_MMC_CAVIUM_THUNDERX=y +CONFIG_MMC_DW=y +# CONFIG_MMC_DW_BLUEFIELD is not set +# CONFIG_MMC_DW_EXYNOS is not set +# CONFIG_MMC_DW_HI3798CV200 is not set +# CONFIG_MMC_DW_K3 is not set +# CONFIG_MMC_DW_PCI is not set +CONFIG_MMC_DW_PLTFM=y +CONFIG_MMC_DW_ROCKCHIP=y CONFIG_MMC_RICOH_MMC=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_ACPI=y CONFIG_MMC_SDHCI_OF_ESDHC=y CONFIG_MMC_SDHCI_PCI=y CONFIG_MMC_SDHCI_PLTFM=y +CONFIG_MMC_SUNXI=y CONFIG_MODULES_USE_ELF_RELA=y +# CONFIG_MVNETA is not set +# CONFIG_MVPP2 is not set +# CONFIG_MV_XOR is not set CONFIG_NEED_SG_DMA_LENGTH=y +# CONFIG_NET_VENDOR_ALLWINNER is not set CONFIG_NO_HZ=y CONFIG_NO_HZ_COMMON=y CONFIG_NO_HZ_IDLE=y CONFIG_NR_CPUS=64 CONFIG_NVIDIA_CARMEL_CNP_ERRATUM=y # CONFIG_NVMEM_LAYERSCAPE_SFP is not set +CONFIG_NVMEM_ROCKCHIP_EFUSE=y +# CONFIG_NVMEM_ROCKCHIP_OTP is not set +# CONFIG_NVMEM_SUNXI_SID is not set +# CONFIG_NVMEM_ZYNQMP is not set +CONFIG_PCC=y +CONFIG_PCIE_HISI_STB=y CONFIG_PCIE_LAYERSCAPE=y +CONFIG_PCIE_MOBIVEIL_PLAT=y +CONFIG_PCIE_ROCKCHIP=y +# CONFIG_PCIE_ROCKCHIP_DW_HOST is not set +CONFIG_PCIE_ROCKCHIP_HOST=y +CONFIG_PCIE_XILINX_CPM=y +CONFIG_PCIE_XILINX_NWL=y +CONFIG_PCI_AARDVARK=y CONFIG_PCI_LAYERSCAPE=y # CONFIG_PHY_FSL_LYNX_28G is not set +CONFIG_PHY_HI3660_USB=y +CONFIG_PHY_HI3670_PCIE=y +CONFIG_PHY_HI3670_USB=y +CONFIG_PHY_HI6220_USB=y +CONFIG_PHY_HISI_INNO_USB2=y +# CONFIG_PHY_HISTB_COMBPHY is not set +CONFIG_PHY_MVEBU_A3700_COMPHY=y +CONFIG_PHY_MVEBU_A3700_UTMI=y +CONFIG_PHY_MVEBU_A38X_COMPHY=y +CONFIG_PHY_MVEBU_CP110_COMPHY=y +# CONFIG_PHY_ROCKCHIP_DP is not set +# CONFIG_PHY_ROCKCHIP_DPHY_RX0 is not set +CONFIG_PHY_ROCKCHIP_EMMC=y +# CONFIG_PHY_ROCKCHIP_INNO_CSIDPHY is not set +# CONFIG_PHY_ROCKCHIP_INNO_DSIDPHY is not set +# CONFIG_PHY_ROCKCHIP_INNO_HDMI is not set +CONFIG_PHY_ROCKCHIP_INNO_USB2=y +# CONFIG_PHY_ROCKCHIP_NANENG_COMBO_PHY is not set +CONFIG_PHY_ROCKCHIP_PCIE=y +CONFIG_PHY_ROCKCHIP_SNPS_PCIE3=y +CONFIG_PHY_ROCKCHIP_TYPEC=y +# CONFIG_PHY_ROCKCHIP_USB is not set +CONFIG_PHY_SUN4I_USB=y +CONFIG_PHY_SUN50I_USB3=y +# CONFIG_PHY_SUN6I_MIPI_DPHY is not set +CONFIG_PHY_SUN9I_USB=y +# CONFIG_PHY_XILINX_ZYNQMP is not set +CONFIG_PINCTRL_ROCKCHIP=y +# CONFIG_PINCTRL_SUN20I_D1 is not set +CONFIG_PINCTRL_SUN4I_A10=y +CONFIG_PINCTRL_SUN50I_A100=y +CONFIG_PINCTRL_SUN50I_A100_R=y +CONFIG_PINCTRL_SUN50I_A64=y +CONFIG_PINCTRL_SUN50I_A64_R=y +CONFIG_PINCTRL_SUN50I_H5=y +CONFIG_PINCTRL_SUN50I_H6=y +CONFIG_PINCTRL_SUN50I_H616=y +CONFIG_PINCTRL_SUN50I_H616_R=y +CONFIG_PINCTRL_SUN50I_H6_R=y +CONFIG_PINCTRL_SUN5I=y +# CONFIG_PINCTRL_SUN6I_A31 is not set +# CONFIG_PINCTRL_SUN6I_A31_R is not set +# CONFIG_PINCTRL_SUN8I_A23 is not set +# CONFIG_PINCTRL_SUN8I_A23_R is not set +# CONFIG_PINCTRL_SUN8I_A33 is not set +# CONFIG_PINCTRL_SUN8I_A83T is not set +# CONFIG_PINCTRL_SUN8I_A83T_R is not set +# CONFIG_PINCTRL_SUN8I_H3 is not set +# CONFIG_PINCTRL_SUN8I_H3_R is not set +# CONFIG_PINCTRL_SUN8I_V3S is not set +# CONFIG_PINCTRL_SUN9I_A80 is not set +# CONFIG_PINCTRL_SUN9I_A80_R is not set +CONFIG_PINCTRL_ZYNQMP=y CONFIG_PM=y CONFIG_PM_CLK=y CONFIG_PM_GENERIC_DOMAINS=y CONFIG_PM_GENERIC_DOMAINS_OF=y CONFIG_POWER_RESET=y +CONFIG_POWER_RESET_HISI=y CONFIG_POWER_RESET_VEXPRESS=y CONFIG_POWER_SUPPLY=y CONFIG_QORIQ_THERMAL=y @@ -186,29 +353,90 @@ CONFIG_QUEUED_RWLOCKS=y CONFIG_QUEUED_SPINLOCKS=y CONFIG_REGMAP=y CONFIG_REGMAP_MMIO=y +CONFIG_REGULATOR_AXP20X=y +CONFIG_REGULATOR_HI655X=y +CONFIG_ROCKCHIP_IODOMAIN=y +CONFIG_ROCKCHIP_IOMMU=y +# CONFIG_ROCKCHIP_MBOX is not set +CONFIG_ROCKCHIP_PM_DOMAINS=y +# CONFIG_ROCKCHIP_SARADC is not set +# CONFIG_ROCKCHIP_THERMAL is not set CONFIG_RODATA_FULL_DEFAULT_ENABLED=y # CONFIG_RTC_DRV_FSL_FTM_ALARM is not set +CONFIG_RTC_DRV_MV=y CONFIG_RTC_I2C_AND_SPI=y +# CONFIG_SERIAL_8250_EXAR is not set CONFIG_SERIAL_8250_FSL=y +CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_MVEBU_CONSOLE=y +CONFIG_SERIAL_MVEBU_UART=y +CONFIG_SERIAL_SAMSUNG=y +CONFIG_SERIAL_SAMSUNG_CONSOLE=y CONFIG_SMC91X=y +# CONFIG_SND_SUN4I_I2S is not set +# CONFIG_SND_SUN50I_CODEC_ANALOG is not set +# CONFIG_SND_SUN50I_DMIC is not set +# CONFIG_SND_SUN8I_CODEC is not set +# CONFIG_SND_SUN8I_CODEC_ANALOG is not set +# CONFIG_SNI_NETSEC is not set CONFIG_SPARSEMEM=y CONFIG_SPARSEMEM_EXTREME=y CONFIG_SPARSEMEM_VMEMMAP=y CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y +CONFIG_SPI_ARMADA_3700=y # CONFIG_SPI_FSL_QUADSPI is not set # CONFIG_SPI_HISI_KUNPENG is not set +# CONFIG_SPI_HISI_SFC is not set # CONFIG_SPI_HISI_SFC_V3XX is not set +# CONFIG_SPI_ROCKCHIP_SFC is not set +# CONFIG_SPI_SUN4I is not set +# CONFIG_SPI_SUN6I is not set +# CONFIG_SPI_SYNQUACER is not set +CONFIG_SPI_THUNDERX=y +# CONFIG_SPI_XLP is not set +CONFIG_STUB_CLK_HI3660=y +CONFIG_STUB_CLK_HI6220=y +CONFIG_SUN50I_A100_CCU=y +CONFIG_SUN50I_A100_R_CCU=y +CONFIG_SUN50I_A64_CCU=y +CONFIG_SUN50I_H616_CCU=y +CONFIG_SUN50I_H6_CCU=y +CONFIG_SUN50I_H6_R_CCU=y +CONFIG_SUN50I_IOMMU=y +CONFIG_SUN6I_MSGBOX=y +CONFIG_SUN6I_RTC_CCU=y +# CONFIG_SUN8I_A83T_CCU is not set +CONFIG_SUN8I_DE2_CCU=y +# CONFIG_SUN8I_H3_CCU is not set +CONFIG_SUN8I_R_CCU=y +CONFIG_SUN8I_THERMAL=y +CONFIG_SUNXI_CCU=y +CONFIG_SUNXI_RSB=y +CONFIG_SUNXI_WATCHDOG=y CONFIG_SYNC_FILE=y CONFIG_SYSCTL_EXCEPTION_TRACE=y +# CONFIG_TCG_TIS_SYNQUACER is not set CONFIG_THREAD_INFO_IN_TASK=y +# CONFIG_TURRIS_MOX_RWTM is not set # CONFIG_UACCE is not set CONFIG_UNMAP_KERNEL_AT_EL0=y CONFIG_USB_DWC3=y +CONFIG_USB_DWC3_XILINX=y CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_HISTB=y +CONFIG_USB_XHCI_MVEBU=y CONFIG_USB_XHCI_PLATFORM=y CONFIG_VEXPRESS_CONFIG=y CONFIG_VIDEOMODE_HELPERS=y CONFIG_VIRTIO_DMA_SHARED_BUFFER=y # CONFIG_VIRTIO_IOMMU is not set CONFIG_VMAP_STACK=y +CONFIG_WDAT_WDT=y +# CONFIG_XILINX_AMS is not set +# CONFIG_XILINX_INTC is not set +CONFIG_XLNX_EVENT_MANAGER=y CONFIG_ZONE_DMA32=y +CONFIG_ZYNQMP_FIRMWARE=y +# CONFIG_ZYNQMP_FIRMWARE_DEBUG is not set +CONFIG_ZYNQMP_PM_DOMAINS=y +CONFIG_ZYNQMP_POWER=y diff --git a/target/linux/armvirt/config-6.1 b/target/linux/armvirt/config-6.1 index 6e6b64a313..028a5a1b2e 100644 --- a/target/linux/armvirt/config-6.1 +++ b/target/linux/armvirt/config-6.1 @@ -198,6 +198,7 @@ CONFIG_MIGRATION=y CONFIG_MMC_SDHCI_ACPI=y CONFIG_MODULES_USE_ELF_RELA=y CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_MVMDIO=y CONFIG_NEED_DMA_MAP_STATE=y CONFIG_NEED_SG_DMA_LENGTH=y CONFIG_NET_9P=y diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index 2de26afb54..ef27d6a728 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -107,7 +107,10 @@ define Device/generic FILESYSTEMS := ext4 squashfs DEVICE_PACKAGES += kmod-amazon-ena kmod-e1000e kmod-vmxnet3 kmod-rtc-rx8025 \ kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils kmod-wdt-sp805 \ - kmod-fsl-dpaa2-net kmod-sfp + kmod-mvneta kmod-mvpp2 kmod-fsl-dpaa1-net kmod-fsl-dpaa2-net \ + kmod-fsl-enetc-net kmod-sfp \ + kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell kmod-phy-marvell-10g \ + kmod-atlantic endef TARGET_DEVICES += generic diff --git a/target/linux/armvirt/modules.mk b/target/linux/armvirt/modules.mk index 3ac3f6a27d..919685bf64 100644 --- a/target/linux/armvirt/modules.mk +++ b/target/linux/armvirt/modules.mk @@ -48,6 +48,44 @@ endef $(eval $(call KernelPackage,fsl-mc-dpio)) +define KernelPackage/fsl-enetc-net + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=:NXP ENETC (LS1028A) Ethernet + DEPENDS:=@(TARGET_armvirt_64) +kmod-phylink +kmod-fsl-pcs-lynx + KCONFIG:= \ + CONFIG_FSL_ENETC \ + CONFIG_FSL_ENETC_VF \ + CONFIG_FSL_ENETC_QOS + FILES:= \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/enetc/fsl-enetc.ko \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/enetc/fsl-enetc-vf.ko \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/enetc/fsl-enetc-mdio.ko \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/enetc/fsl-enetc-ierb.ko + AUTOLOAD=$(call AutoLoad,35,fsl-enetc) +endef + +$(eval $(call KernelPackage,fsl-enetc-net)) + +define KernelPackage/fsl-dpaa1-net + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=NXP DPAA1 (LS1043/LS1046) Ethernet + DEPENDS:=@(TARGET_armvirt_64) +kmod-fsl-xgmac-mdio +kmod-libphy +kmod-crypto-crc32 + KCONFIG:= \ + CONFIG_FSL_DPAA=y \ + CONFIG_FSL_DPAA_ETH \ + CONFIG_FSL_FMAN \ + CONFIG_FSL_DPAA_CHECKING=n \ + CONFIG_FSL_BMAN_TEST=n \ + CONFIG_FSL_QMAN_TEST=n + MODULES:= \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/dpaa/fsl_dpa.ko \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/fman/fsl_dpaa_fman.ko \ + $(LINUX_DIR)/drivers/net/ethernet/freescale/fman/fsl_dpaa_mac.ko + AUTOLOAD=$(call AutoLoad,35,fsl-dpa) +endef + +$(eval $(call KernelPackage,fsl-dpaa1-net)) + define KernelPackage/fsl-dpaa2-net SUBMENU:=$(NETWORK_DEVICES_MENU) TITLE:=NXP DPAA2 Ethernet @@ -79,6 +117,51 @@ endef $(eval $(call KernelPackage,fsl-dpaa2-console)) +define KernelPackage/marvell-mdio + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Marvell Armada platform MDIO driver + DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy +kmod-of-mdio +kmod-acpi-mdio + KCONFIG:=CONFIG_MVMDIO + FILES=$(LINUX_DIR)/drivers/net/ethernet/marvell/mvmdio.ko + AUTOLOAD=$(call AutoLoad,30,marvell-mdio) +endef + +$(eval $(call KernelPackage,marvell-mdio)) + +define KernelPackage/phy-marvell-10g + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Marvell Alaska 10G PHY driver + DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy + KCONFIG:=CONFIG_MARVELL_10G_PHY + FILES=$(LINUX_DIR)/drivers/net/phy/marvell10g.ko + AUTOLOAD=$(call AutoLoad,35,marvell10g) +endef + +$(eval $(call KernelPackage,phy-marvell-10g)) + +define KernelPackage/mvneta + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Marvell Armada 370/38x/XP/37xx network driver + DEPENDS:=@(TARGET_armvirt_64) +kmod-marvell-mdio +kmod-phylink + KCONFIG:=CONFIG_MVNETA + FILES:=$(LINUX_DIR)/drivers/net/ethernet/marvell/mvneta.ko + AUTOLOAD=$(call AutoLoad,30,mvneta) +endef + +$(eval $(call KernelPackage,mvneta)) + +define KernelPackage/mvpp2 + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Marvell Armada 375/7K/8K network driver + DEPENDS:=@(TARGET_armvirt_64) +kmod-marvell-mdio +kmod-phylink + KCONFIG:=CONFIG_MVPP2 \ + CONFIG_MVPP2_PTP=n + FILES=$(LINUX_DIR)/drivers/net/ethernet/marvell/mvpp2/mvpp2.ko + AUTOLOAD=$(call AutoLoad,40,mvpp2) +endef + +$(eval $(call KernelPackage,mvpp2)) + define KernelPackage/wdt-sp805 SUBMENU:=$(OTHER_MENU) TITLE:=ARM SP805 Watchdog @@ -94,3 +177,4 @@ define KernelPackage/wdt-sp805/description endef $(eval $(call KernelPackage,wdt-sp805)) + From 3efb3b801bb1393897ff58b9af3753157f28f441 Mon Sep 17 00:00:00 2001 From: Anton Antonov Date: Tue, 30 May 2023 02:55:16 +0000 Subject: [PATCH 27/37] armvirt: 64: Add NXP i.MX 8M Mini/Nano/Quad/Plus EVK support Also includes Advantech RSB-3720 (iMX8 Plus) support. Signed-off-by: Anton Antonov Signed-off-by: Mathew McBride [Re-sort into kernel config, move network into modules] --- target/linux/armvirt/64/config-6.1 | 2 + target/linux/armvirt/base-files/etc/inittab | 3 + target/linux/armvirt/image/Makefile | 6 +- target/linux/armvirt/modules.mk | 72 +++++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/target/linux/armvirt/64/config-6.1 b/target/linux/armvirt/64/config-6.1 index 343a333542..b10c84b858 100644 --- a/target/linux/armvirt/64/config-6.1 +++ b/target/linux/armvirt/64/config-6.1 @@ -368,6 +368,8 @@ CONFIG_RTC_I2C_AND_SPI=y # CONFIG_SERIAL_8250_EXAR is not set CONFIG_SERIAL_8250_FSL=y CONFIG_SERIAL_8250_PCI=y +CONFIG_SERIAL_FSL_LPUART=y +CONFIG_SERIAL_FSL_LPUART_CONSOLE=y CONFIG_SERIAL_MVEBU_CONSOLE=y CONFIG_SERIAL_MVEBU_UART=y CONFIG_SERIAL_SAMSUNG=y diff --git a/target/linux/armvirt/base-files/etc/inittab b/target/linux/armvirt/base-files/etc/inittab index 837d7f32a4..83b1888c5c 100644 --- a/target/linux/armvirt/base-files/etc/inittab +++ b/target/linux/armvirt/base-files/etc/inittab @@ -3,3 +3,6 @@ ttyAMA0::askfirst:/usr/libexec/login.sh ttyS0::askfirst:/usr/libexec/login.sh hvc0::askfirst:/usr/libexec/login.sh +ttymxc0::askfirst:/usr/libexec/login.sh +ttymxc1::askfirst:/usr/libexec/login.sh +ttymxc2::askfirst:/usr/libexec/login.sh diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index ef27d6a728..7c4ba60b9b 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -108,9 +108,9 @@ define Device/generic DEVICE_PACKAGES += kmod-amazon-ena kmod-e1000e kmod-vmxnet3 kmod-rtc-rx8025 \ kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils kmod-wdt-sp805 \ kmod-mvneta kmod-mvpp2 kmod-fsl-dpaa1-net kmod-fsl-dpaa2-net \ - kmod-fsl-enetc-net kmod-sfp \ - kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell kmod-phy-marvell-10g \ - kmod-atlantic + kmod-fsl-enetc-net kmod-dwmac-imx kmod-fsl-fec kmod-sfp \ + kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell \ + kmod-phy-marvell-10g kmod-atlantic endef TARGET_DEVICES += generic diff --git a/target/linux/armvirt/modules.mk b/target/linux/armvirt/modules.mk index 919685bf64..1ff523c7bd 100644 --- a/target/linux/armvirt/modules.mk +++ b/target/linux/armvirt/modules.mk @@ -25,6 +25,29 @@ endef $(eval $(call KernelPackage,fsl-pcs-lynx)) +define KernelPackage/pcs-xpcs + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Synopsis DesignWare PCS driver + DEPENDS:=@(TARGET_armvirt_64) +kmod-phylink + KCONFIG:=CONFIG_PCS_XPCS + FILES:=$(LINUX_DIR)/drivers/net/pcs/pcs_xpcs.ko + AUTOLOAD:=$(call AutoLoad,20,pcs_xpcs) +endef + +$(eval $(call KernelPackage,pcs-xpcs)) + +define KernelPackage/fsl-fec + SUBMENU:=$(NETWORK_DEVICES_MENU) + DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy +kmod-of-mdio \ + +kmod-ptp +kmod-net-selftests + TITLE:=NXP (Freescale) FEC Ethernet controller (i.MX) + KCONFIG:=CONFIG_FEC + FILES:=$(LINUX_DIR)/drivers/net/ethernet/freescale/fec.ko + AUTOLOAD:=$(call AutoLoad,35,fec) +endef + +$(eval $(call KernelPackage,fsl-fec)) + define KernelPackage/fsl-xgmac-mdio SUBMENU=$(NETWORK_DEVICES_MENU) DEPENDS:=@(TARGET_armvirt_64) +kmod-libphy +kmod-of-mdio +kmod-acpi-mdio @@ -162,6 +185,55 @@ endef $(eval $(call KernelPackage,mvpp2)) +define KernelPackage/imx2-wdt + SUBMENU:=$(OTHER_MENU) + TITLE:=NXP (Freescale) i.MX2+ and Layerscape watchdog driver + KCONFIG:=CONFIG_IMX2_WDT + FILES=$(LINUX_DIR)/drivers/watchdog/imx2_wdt.ko + AUTOLOAD=$(call AutoLoad,60,imx2_wdt) +endef + +$(eval $(call KernelPackage,imx2-wdt)) + +define KernelPackage/imx7-ulp-wdt + SUBMENU:=$(OTHER_MENU) + TITLE:=NXP (Freescale) i.MX7ULP and later watchdog + KCONFIG:=CONFIG_IMX7ULP_WDT + FILES=$(LINUX_DIR)/drivers/watchdog/imx7ulp_wdt.ko + AUTOLOAD=$(call AutoLoad,60,imx7ulp_wdt) +endef + +$(eval $(call KernelPackage,imx7-ulp-wdt)) + +define KernelPackage/stmmac-core + SUBMENU=$(NETWORK_DEVICES_MENU) + TITLE:=Synopsis Ethernet Controller core (NXP,STMMicro,others) + DEPENDS:=@(TARGET_armvirt_64) +kmod-pcs-xpcs +kmod-ptp \ + +kmod-of-mdio + KCONFIG:=CONFIG_STMMAC_ETH \ + CONFIG_STMMAC_SELFTESTS=n \ + CONFIG_STMMAC_PLATFORM \ + CONFIG_CONFIG_DWMAC_DWC_QOS_ETH=n \ + CONFIG_DWMAC_GENERIC + FILES=$(LINUX_DIR)/drivers/net/ethernet/stmicro/stmmac/stmmac.ko \ + $(LINUX_DIR)/drivers/net/ethernet/stmicro/stmmac/stmmac-platform.ko \ + $(LINUX_DIR)/drivers/net/ethernet/stmicro/stmmac/dwmac-generic.ko + AUTOLOAD=$(call AutoLoad,40,stmmac stmmac-platform dwmac-generic) +endef + +$(eval $(call KernelPackage,stmmac-core)) + +define KernelPackage/dwmac-imx + SUBMENU=$(NETWORK_DEVICES_MENU) + TITLE:=NXP i.MX8 Ethernet controller + DEPENDS:=+kmod-stmmac-core + KCONFIG:=CONFIG_DWMAC_IMX8 + FILES=$(LINUX_DIR)/drivers/net/ethernet/stmicro/stmmac/dwmac-imx.ko + AUTOLOAD=$(call AutoLoad,45,dwmac-imx) +endef + +$(eval $(call KernelPackage,dwmac-imx)) + define KernelPackage/wdt-sp805 SUBMENU:=$(OTHER_MENU) TITLE:=ARM SP805 Watchdog From 26905c96124af10a795167509116252e9357baea Mon Sep 17 00:00:00 2001 From: Anton Antonov Date: Thu, 22 Dec 2022 12:01:59 +0000 Subject: [PATCH 28/37] armvirt: 64: Add storage support for qemu-sbsa platform Enable SATA support, which is used by the Server Base System Architecture reference board[1]. Signed-off-by: Anton Antonov Signed-off-by: Mathew McBride [1] - https://qemu.readthedocs.io/en/latest/system/arm/sbsa.html --- target/linux/armvirt/config-6.1 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/target/linux/armvirt/config-6.1 b/target/linux/armvirt/config-6.1 index 028a5a1b2e..df09b946ac 100644 --- a/target/linux/armvirt/config-6.1 +++ b/target/linux/armvirt/config-6.1 @@ -71,6 +71,8 @@ CONFIG_ARM_GIC_V3=y CONFIG_ARM_GIC_V3_ITS=y CONFIG_ARM_GIC_V3_ITS_PCI=y CONFIG_ARM_PSCI_FW=y +# CONFIG_ARM_SMMU_V3_PMU is not set +CONFIG_ATA=y CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y CONFIG_BALLOON_COMPACTION=y CONFIG_BLK_DEV_LOOP=y @@ -252,6 +254,9 @@ CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_EFI=y CONFIG_RTC_DRV_PL031=y CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_SATA_AHCI=y +CONFIG_SATA_AHCI_PLATFORM=y +CONFIG_SATA_HOST=y CONFIG_SCSI=y CONFIG_SCSI_COMMON=y CONFIG_SCSI_VIRTIO=y From 5d2a5f739840caa6e72b5c907d355f6aaca227d4 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Sat, 11 Feb 2023 03:58:09 +0000 Subject: [PATCH 29/37] armvirt: 64: add Marvell (formerly Cavium) ThunderX series network driver Based on working configuration supplied by Anton Antonov. Signed-off-by: Mathew McBride --- target/linux/armvirt/image/Makefile | 4 ++-- target/linux/armvirt/modules.mk | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index 7c4ba60b9b..f1d0acd013 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -108,8 +108,8 @@ define Device/generic DEVICE_PACKAGES += kmod-amazon-ena kmod-e1000e kmod-vmxnet3 kmod-rtc-rx8025 \ kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils kmod-wdt-sp805 \ kmod-mvneta kmod-mvpp2 kmod-fsl-dpaa1-net kmod-fsl-dpaa2-net \ - kmod-fsl-enetc-net kmod-dwmac-imx kmod-fsl-fec kmod-sfp \ - kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell \ + kmod-fsl-enetc-net kmod-dwmac-imx kmod-fsl-fec kmod-thunderx-net \ + kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell kmod-sfp \ kmod-phy-marvell-10g kmod-atlantic endef TARGET_DEVICES += generic diff --git a/target/linux/armvirt/modules.mk b/target/linux/armvirt/modules.mk index 1ff523c7bd..1531e91bde 100644 --- a/target/linux/armvirt/modules.mk +++ b/target/linux/armvirt/modules.mk @@ -234,6 +234,24 @@ endef $(eval $(call KernelPackage,dwmac-imx)) +define KernelPackage/thunderx-net + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Marvell (Cavium) ThunderX/2 network drivers + DEPENDS:=@(TARGET_armvirt_64) +kmod-phylink +kmod-of-mdio + KCONFIG:=CONFIG_NET_VENDOR_CAVIUM \ + CONFIG_THUNDER_NIC_PF \ + CONFIG_THUNDER_NIC_VF \ + CONFIG_THUNDER_NIC_BGX \ + CONFIG_THUNDER_NIC_RGX + FILES=$(LINUX_DIR)/drivers/net/ethernet/cavium/thunder/nicvf.ko \ + $(LINUX_DIR)/drivers/net/ethernet/cavium/thunder/nicpf.ko \ + $(LINUX_DIR)/drivers/net/ethernet/cavium/thunder/thunder_xcv.ko \ + $(LINUX_DIR)/drivers/net/ethernet/cavium/thunder/thunder_bgx.ko + AUTOLOAD=$(call AutoLoad,40,nicpf nicvf thunder_xcv thunder_bgx) +endef + +$(eval $(call KernelPackage,thunderx-net)) + define KernelPackage/wdt-sp805 SUBMENU:=$(OTHER_MENU) TITLE:=ARM SP805 Watchdog From 2dbeb607251b75b506dcc8f1294cd9ed0bac9694 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Mon, 20 Mar 2023 04:16:04 +0000 Subject: [PATCH 30/37] kernel: add mdio-bus-mux support The MDIO bus multiplexing framework is used by some drivers such as dwmac-sun8i. As this is a per-driver requirement, set it to be hidden in the menu. Signed-off-by: Mathew McBride --- package/kernel/linux/modules/netsupport.mk | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/package/kernel/linux/modules/netsupport.mk b/package/kernel/linux/modules/netsupport.mk index 3cbbf6c104..8fcbb3e08c 100644 --- a/package/kernel/linux/modules/netsupport.mk +++ b/package/kernel/linux/modules/netsupport.mk @@ -1363,6 +1363,21 @@ endef $(eval $(call KernelPackage,mdio)) +define KernelPackage/mdio-bus-mux + SUBMENU:=$(NETWORK_SUPPORT_MENU) + TITLE:=MDIO bus multiplexers + KCONFIG:=CONFIG_MDIO_BUS_MUX + HIDDEN:=1 + FILES:=$(LINUX_DIR)/drivers/net/mdio/mdio-mux.ko + AUTOLOAD:=$(call AutoLoad,32,mdio-mux) +endef + +define KernelPackage/mdio/description + Kernel framework for MDIO bus multiplexers. +endef + +$(eval $(call KernelPackage,mdio-bus-mux)) + define KernelPackage/macsec SUBMENU:=$(NETWORK_SUPPORT_MENU) TITLE:=IEEE 802.1AE MAC-level encryption (MAC) From 847467a5729995a98aa34329f6fa0ed4cb79d210 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Mon, 20 Mar 2023 02:53:31 +0000 Subject: [PATCH 31/37] armvirt: 64: add Allwinner A3/A83T/A64 (sun8i family) Ethernet Add support for the dwmac (stmmac) variant used by Allwinner Arm64 boards. Signed-off-by: Mathew McBride --- target/linux/armvirt/image/Makefile | 4 ++-- target/linux/armvirt/modules.mk | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index f1d0acd013..533a17b522 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -109,8 +109,8 @@ define Device/generic kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils kmod-wdt-sp805 \ kmod-mvneta kmod-mvpp2 kmod-fsl-dpaa1-net kmod-fsl-dpaa2-net \ kmod-fsl-enetc-net kmod-dwmac-imx kmod-fsl-fec kmod-thunderx-net \ - kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell kmod-sfp \ - kmod-phy-marvell-10g kmod-atlantic + kmod-dwmac-sun8i kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell \ + kmod-sfp kmod-phy-marvell-10g kmod-atlantic endef TARGET_DEVICES += generic diff --git a/target/linux/armvirt/modules.mk b/target/linux/armvirt/modules.mk index 1531e91bde..5c863824d3 100644 --- a/target/linux/armvirt/modules.mk +++ b/target/linux/armvirt/modules.mk @@ -234,6 +234,17 @@ endef $(eval $(call KernelPackage,dwmac-imx)) +define KernelPackage/dwmac-sun8i + SUBMENU=$(NETWORK_DEVICES_MENU) + TITLE:=Allwinner H3/A83T/A64 (sun8i) Ethernet + DEPENDS:=+kmod-stmmac-core +kmod-mdio-bus-mux + KCONFIG:=CONFIG_DWMAC_SUN8I + FILES=$(LINUX_DIR)/drivers/net/ethernet/stmicro/stmmac/dwmac-sun8i.ko + AUTOLOAD=$(call AutoLoad,45,dwmac-sun8i) +endef + +$(eval $(call KernelPackage,dwmac-sun8i)) + define KernelPackage/thunderx-net SUBMENU:=$(NETWORK_DEVICES_MENU) TITLE:=Marvell (Cavium) ThunderX/2 network drivers From abbffe55ddded36d2a4d0eee6e96c742eaffbbd2 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Tue, 18 Apr 2023 03:34:23 +0000 Subject: [PATCH 32/37] armvirt: package and select Rockchip DWMAC Ethernet driver For devices that implement the "rockchip,*-gmac" compatible controller, including: - RK3328 - RK3399 - RK3568 - RK3588 - PX30 Signed-off-by: Mathew McBride --- target/linux/armvirt/image/Makefile | 4 ++-- target/linux/armvirt/modules.mk | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/target/linux/armvirt/image/Makefile b/target/linux/armvirt/image/Makefile index 533a17b522..8642c88a49 100644 --- a/target/linux/armvirt/image/Makefile +++ b/target/linux/armvirt/image/Makefile @@ -109,8 +109,8 @@ define Device/generic kmod-i2c-mux-pca954x kmod-gpio-pca953x partx-utils kmod-wdt-sp805 \ kmod-mvneta kmod-mvpp2 kmod-fsl-dpaa1-net kmod-fsl-dpaa2-net \ kmod-fsl-enetc-net kmod-dwmac-imx kmod-fsl-fec kmod-thunderx-net \ - kmod-dwmac-sun8i kmod-phy-aquantia kmod-phy-broadcom kmod-phy-marvell \ - kmod-sfp kmod-phy-marvell-10g kmod-atlantic + kmod-dwmac-rockchip kmod-dwmac-sun8i kmod-phy-aquantia kmod-phy-broadcom \ + kmod-phy-marvell kmod-phy-marvell-10g kmod-sfp kmod-atlantic endef TARGET_DEVICES += generic diff --git a/target/linux/armvirt/modules.mk b/target/linux/armvirt/modules.mk index 5c863824d3..c59301aae7 100644 --- a/target/linux/armvirt/modules.mk +++ b/target/linux/armvirt/modules.mk @@ -245,6 +245,17 @@ endef $(eval $(call KernelPackage,dwmac-sun8i)) +define KernelPackage/dwmac-rockchip + SUBMENU=$(NETWORK_DEVICES_MENU) + TITLE:=Rockchip RK3328/RK3399/RK3568 Ethernet + DEPENDS:=+kmod-stmmac-core +kmod-mdio-bus-mux + KCONFIG:=CONFIG_DWMAC_ROCKCHIP + FILES=$(LINUX_DIR)/drivers/net/ethernet/stmicro/stmmac/dwmac-rk.ko + AUTOLOAD=$(call AutoLoad,45,dwmac-rk) +endef + +$(eval $(call KernelPackage,dwmac-rockchip)) + define KernelPackage/thunderx-net SUBMENU:=$(NETWORK_DEVICES_MENU) TITLE:=Marvell (Cavium) ThunderX/2 network drivers From 83f564f7464c34c7713b20b61007b24b217f0b88 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Mon, 5 Sep 2022 16:55:02 +1000 Subject: [PATCH 33/37] armvirt: config changes required for framebuffer console These Kconfig options are required to get a screen console working with the VMware Fusion ARM (Apple Silicon) preview. They are likely to be the same for other Arm standard "desktop" hardware that may emerge. Signed-off-by: Mathew McBride --- target/linux/armvirt/config-6.1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/target/linux/armvirt/config-6.1 b/target/linux/armvirt/config-6.1 index df09b946ac..a48af89b87 100644 --- a/target/linux/armvirt/config-6.1 +++ b/target/linux/armvirt/config-6.1 @@ -84,6 +84,7 @@ CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y CONFIG_CLONE_BACKWARDS=y CONFIG_COMMON_CLK=y # CONFIG_COMPAT_32BIT_TIME is not set +CONFIG_CONSOLE_TRANSLATIONS=y CONFIG_CPU_IDLE=y CONFIG_CPU_IDLE_GOV_LADDER=y CONFIG_CPU_PM=y @@ -132,6 +133,9 @@ CONFIG_FIX_EARLYCON_MEM=y CONFIG_FONT_8x16=y CONFIG_FONT_AUTOSELECT=y CONFIG_FONT_SUPPORT=y +CONFIG_FRAMEBUFFER_CONSOLE=y +CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY=y +# CONFIG_FRAMEBUFFER_CONSOLE_ROTATION is not set CONFIG_FRAME_POINTER=y CONFIG_FS_IOMAP=y CONFIG_FS_MBCACHE=y @@ -172,6 +176,8 @@ CONFIG_HARDIRQS_SW_RESEND=y CONFIG_HAS_DMA=y CONFIG_HAS_IOMEM=y CONFIG_HAS_IOPORT_MAP=y +CONFIG_HID=y +CONFIG_HID_GENERIC=y CONFIG_HOTPLUG_CPU=y CONFIG_HOTPLUG_PCI_ACPI=y CONFIG_HVC_DRIVER=y @@ -183,6 +189,7 @@ CONFIG_I2C_HID_ACPI=y CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set CONFIG_INITRAMFS_SOURCE="" +CONFIG_INPUT_KEYBOARD=y CONFIG_IRQCHIP=y CONFIG_IRQ_DOMAIN=y CONFIG_IRQ_DOMAIN_HIERARCHY=y @@ -303,6 +310,9 @@ CONFIG_TREE_SRCU=y CONFIG_UCS2_STRING=y CONFIG_UNMAP_KERNEL_AT_EL0=y CONFIG_USB=y +CONFIG_USB_HID=y +CONFIG_USB_HIDDEV=y +CONFIG_USB_PCI=y CONFIG_USB_STORAGE=y CONFIG_USB_SUPPORT=y CONFIG_USB_XHCI_HCD=y @@ -319,5 +329,8 @@ CONFIG_VIRTIO_PCI_LEGACY=y CONFIG_VIRTIO_PCI_LIB=y CONFIG_VMAP_STACK=y CONFIG_WATCHDOG_CORE=y +CONFIG_VT=y +CONFIG_VT_CONSOLE=y +# CONFIG_VT_HW_CONSOLE_BINDING is not set CONFIG_XPS=y CONFIG_ZONE_DMA32=y From e41b82f619ca02f427f34ae439d4584ab441e245 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Mon, 5 Sep 2022 16:56:49 +1000 Subject: [PATCH 34/37] armvirt: base-files: add tty0 to inittab tty0 is the default console for devices with screens/framebuffers. Signed-off-by: Mathew McBride --- target/linux/armvirt/base-files/etc/inittab | 1 + 1 file changed, 1 insertion(+) diff --git a/target/linux/armvirt/base-files/etc/inittab b/target/linux/armvirt/base-files/etc/inittab index 83b1888c5c..51832eb775 100644 --- a/target/linux/armvirt/base-files/etc/inittab +++ b/target/linux/armvirt/base-files/etc/inittab @@ -2,6 +2,7 @@ ::shutdown:/etc/init.d/rcS K shutdown ttyAMA0::askfirst:/usr/libexec/login.sh ttyS0::askfirst:/usr/libexec/login.sh +tty0::askfirst:/usr/libexec/login.sh hvc0::askfirst:/usr/libexec/login.sh ttymxc0::askfirst:/usr/libexec/login.sh ttymxc1::askfirst:/usr/libexec/login.sh From 214e94cddf1bfd4e6141f79a70f532267fe1bea0 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Mon, 13 Feb 2023 06:51:26 +0000 Subject: [PATCH 35/37] armvirt: 64: disable CONFIG_SMC91X The SMC91X family is a ISA-age Ethernet controller. I'm not particularly sure what it's doing in armvirt/64, as it's unlikely there is a QEMU or real hardware configuration that exists with it. Signed-off-by: Mathew McBride --- target/linux/armvirt/64/config-6.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target/linux/armvirt/64/config-6.1 b/target/linux/armvirt/64/config-6.1 index b10c84b858..f1b4838d15 100644 --- a/target/linux/armvirt/64/config-6.1 +++ b/target/linux/armvirt/64/config-6.1 @@ -374,7 +374,7 @@ CONFIG_SERIAL_MVEBU_CONSOLE=y CONFIG_SERIAL_MVEBU_UART=y CONFIG_SERIAL_SAMSUNG=y CONFIG_SERIAL_SAMSUNG_CONSOLE=y -CONFIG_SMC91X=y +# CONFIG_SMC91X is not set # CONFIG_SND_SUN4I_I2S is not set # CONFIG_SND_SUN50I_CODEC_ANALOG is not set # CONFIG_SND_SUN50I_DMIC is not set From 3a7c8fd15e89237c8c9db62393d057f3a47429d2 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Wed, 5 Oct 2022 12:40:22 +1100 Subject: [PATCH 36/37] kernel: kmod-amazon-ena: move to top level netdevices The Amazon ENA network devices are also used on the AWS Arm (Graviton) instance types, so move it from the x86-only module file to the top level netdevices. Signed-off-by: Mathew McBride --- package/kernel/linux/modules/netdevices.mk | 15 +++++++++++++++ target/linux/x86/modules.mk | 17 ----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/package/kernel/linux/modules/netdevices.mk b/package/kernel/linux/modules/netdevices.mk index 948902cf63..d2cc67d164 100644 --- a/package/kernel/linux/modules/netdevices.mk +++ b/package/kernel/linux/modules/netdevices.mk @@ -1541,3 +1541,18 @@ endef $(eval $(call KernelPackage,lan743x)) +define KernelPackage/amazon-ena + SUBMENU:=$(NETWORK_DEVICES_MENU) + TITLE:=Elastic Network Adapter (for Amazon AWS) + DEPENDS:=@TARGET_x86_64||TARGET_armvirt_64 + KCONFIG:=CONFIG_ENA_ETHERNET + FILES:=$(LINUX_DIR)/drivers/net/ethernet/amazon/ena/ena.ko + AUTOLOAD:=$(call AutoLoad,12,ena) +endef + +define KernelPackage/amazon-ena/description + This driver supports Elastic Network Adapter (ENA) + used by Amazon AWS T3 (2018) and later instances. +endef + +$(eval $(call KernelPackage,amazon-ena)) diff --git a/target/linux/x86/modules.mk b/target/linux/x86/modules.mk index f6a7c6c440..511410d614 100644 --- a/target/linux/x86/modules.mk +++ b/target/linux/x86/modules.mk @@ -2,23 +2,6 @@ # # Copyright (C) 2017 Cezary Jackiewicz -define KernelPackage/amazon-ena - SUBMENU:=$(NETWORK_DEVICES_MENU) - TITLE:=Elastic Network Adapter (for Amazon AWS T3) - DEPENDS:=@TARGET_x86_64 - KCONFIG:=CONFIG_ENA_ETHERNET - FILES:=$(LINUX_DIR)/drivers/net/ethernet/amazon/ena/ena.ko - AUTOLOAD:=$(call AutoLoad,12,ena) -endef - -define KernelPackage/amazon-ena/description - This driver supports Elastic Network Adapter (ENA) - used by Amazon AWS T3 instances. -endef - -$(eval $(call KernelPackage,amazon-ena)) - - define KernelPackage/amd-xgbe SUBMENU:=$(NETWORK_DEVICES_MENU) TITLE:=AMD Ethernet on SoC support From abcb30d36cfe65e3ed7786c929c1a2350dd2a9c2 Mon Sep 17 00:00:00 2001 From: Mathew McBride Date: Thu, 1 Jun 2023 05:55:22 +0000 Subject: [PATCH 37/37] armvirt: switch to kernel 6.1 The EFI implementation changes have only been applied to 6.1, so switch armvirt over to it. Signed-off-by: Mathew McBride --- target/linux/armvirt/Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/target/linux/armvirt/Makefile b/target/linux/armvirt/Makefile index ff362428a9..0ae51597bc 100644 --- a/target/linux/armvirt/Makefile +++ b/target/linux/armvirt/Makefile @@ -9,8 +9,7 @@ BOARDNAME:=QEMU ARM Virtual Machine FEATURES:=fpu pci pcie rtc usb boot-part rootfs-part FEATURES+=cpiogz ext4 ramdisk squashfs targz vmdk -KERNEL_PATCHVER:=5.15 -KERNEL_TESTING_PATCHVER:=6.1 +KERNEL_PATCHVER:=6.1 include $(INCLUDE_DIR)/target.mk