From 626d9efa74685720020e816f3a917b7591d3cf7a Mon Sep 17 00:00:00 2001 From: James <> Date: Sat, 26 Sep 2015 12:29:31 +0100 Subject: trunk-47048 --- target/linux/ipq806x/Makefile | 22 + target/linux/ipq806x/base-files.mk | 3 + target/linux/ipq806x/base-files/etc/inittab | 4 + .../linux/ipq806x/base-files/etc/uci-defaults/leds | 26 + .../ipq806x/base-files/etc/uci-defaults/network | 39 + target/linux/ipq806x/base-files/lib/ipq806x.sh | 46 + .../lib/preinit/03_preinit_do_ipq806x.sh | 12 + .../ipq806x/base-files/lib/upgrade/platform.sh | 30 + target/linux/ipq806x/config-3.18 | 444 +++++ target/linux/ipq806x/config-4.1 | 449 +++++ target/linux/ipq806x/image/Makefile | 124 ++ target/linux/ipq806x/modules.mk | 32 + .../001-spi-qup-Add-DMA-capabilities.patch | 522 +++++ ...-v3-spi-qup-Fix-incorrect-block-transfers.patch | 376 ++++ .../003-spi-qup-Ensure-done-detection.patch | 56 + ...atchdog-qcom-use-timer-devicetree-binding.patch | 67 + ...m-add-description-of-KPSS-WDT-for-IPQ8064.patch | 48 + ...-watchdog-entries-to-DT-timer-binding-doc.patch | 50 + .../patches-3.18/020-add-ap148-bootargs.patch | 46 + .../patches-3.18/021-add-ap148-partitions.patch | 19 + .../ipq806x/patches-3.18/022-add-db149-dts.patch | 160 ++ ...M-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch | 53 + .../patches-3.18/024-ap148-add-memory-node.patch | 14 + ...0-hwspinlock-core-add-device-tree-support.patch | 167 ++ ...qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch | 234 +++ ...spinlock-qcom-Correct-msb-in-regmap_field.patch | 26 + ...33-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch | 34 + ...soc-qcom-Add-device-tree-binding-for-SMEM.patch | 82 + ...soc-qcom-Add-Shared-Memory-Manager-driver.patch | 841 ++++++++ ...-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch | 36 + ...37-mtd-add-SMEM-parser-for-QCOM-platforms.patch | 277 +++ ...b-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch | 511 +++++ ...1-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch | 126 ++ ...qcom-gsbi-Add-support-for-ADM-CRCI-muxing.patch | 249 +++ .../103-ARM-DT-ipq8064-Add-TCSR-support.patch | 65 + ...CI-qcom-Document-PCIe-devicetree-bindings.patch | 263 +++ ...-qcom-Add-Qualcomm-PCIe-controller-driver.patch | 753 ++++++++ ...-qcom-add-pcie-nodes-to-ipq806x-platforms.patch | 266 +++ ...tomatically-select-PCI_DOMAINS-if-PCI-is-.patch | 29 + .../patches-3.18/114-pcie-add-ctlr-init.patch | 278 +++ .../patches-3.18/115-add-pcie-aux-clk-dts.patch | 80 + ...-mfd-qcom-rpm-Driver-for-the-Qualcomm-RPM.patch | 654 +++++++ .../121-mfd-qcom_rpm-Add-support-for-IPQ8064.patch | 71 + ...tree-bindings-Add-Qualcomm-RPM-DT-binding.patch | 247 +++ ...etree-qcom_rpm-Document-IPQ8064-resources.patch | 42 + ...rpm-add-support-for-RPM-controller-SMB208.patch | 58 + ...com-rpm-Add-missing-state-flag-in-call-to.patch | 25 + .../patches-3.18/126-add-rpm-to-ipq8064-dts.patch | 87 + ...-set_parent-doing-the-wrong-thing-when-IN.patch | 55 + ...-clk-Add-__clk_mux_determine_rate_closest.patch | 120 ++ ..._unregister_-divider-gate-mux-to-close-me.patch | 115 ++ ...-Add-Krait-L2-register-accessor-functions.patch | 144 ++ ...ux-Split-out-register-accessors-for-reuse.patch | 192 ++ ...ates-to-downstream-clocks-during-set_rate.patch | 129 ++ .../136-clk-Add-safe-switch-hook.patch | 170 ++ ...dd-support-for-High-Frequency-PLLs-HFPLLs.patch | 351 ++++ .../138-clk-qcom-Add-HFPLL-driver.patch | 206 ++ .../139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch | 127 ++ ...140-clk-qcom-Add-support-for-Krait-clocks.patch | 271 +++ .../141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch | 205 ++ ...lk-qcom-Add-Krait-clock-controller-driver.patch | 435 +++++ ...-module-to-register-cpufreq-on-Krait-CPUs.patch | 304 +++ ...m-Add-necessary-DT-data-for-Krait-cpufreq.patch | 100 + ...ufreq-Add-a-cpufreq-krait-based-on-cpufre.patch | 461 +++++ .../150-dmaengine-Rework-dma_chan_get.patch | 70 + ...Remove-the-need-to-declare-device_control.patch | 27 + ...ake-channel-allocation-callbacks-optional.patch | 62 + ...engine-Introduce-a-device_config-callback.patch | 51 + ...aengine-Add-device_terminate_all-callback.patch | 47 + ...-bindings-qcom_adm-Fix-channel-specifiers.patch | 76 + .../156-dmaengine-Add-ADM-driver.patch | 958 +++++++++ .../157-ARM-DT-ipq8064-Add-ADM-device-node.patch | 42 + .../160-clk-qcom-Add-EBI2-clocks-for-IPQ806x.patch | 74 + ...g-to-access-bad-block-markers-in-raw-mode.patch | 84 + ...-mtd-nand-Qualcomm-NAND-controller-driver.patch | 2024 ++++++++++++++++++++ ...63-dt-bindings-qcom_nandc-Add-DT-bindings.patch | 82 + ...-dts-Add-NAND-controller-node-for-ipq806x.patch | 51 + ...nable-NAND-node-on-IPQ8064-AP148-platform.patch | 79 + ...h-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch | 11 + .../300-arch-arm-force-ZRELADDR-on-arch-qcom.patch | 62 + ...dd-Netgear-Nighthawk-X4-R7500-device-tree.patch | 387 ++++ .../302-mtd-qcom-smem-rename-rootfs-ubi.patch | 13 + ...dd-support-for-NSS-GMAC-clocks-and-resets.patch | 733 +++++++ ...-phy-handle-support-to-the-platform-layer.patch | 105 + ...-error-path-at-the-end-of-stmmac_probe_co.patch | 65 + ...stmmac-add-fixed-link-device-tree-support.patch | 64 + .../704-stmmac-add-ipq806x-glue-layer.patch | 427 +++++ ...mac-ipq806x-document-device-tree-bindings.patch | 52 + ...mac-create-one-debugfs-dir-per-net-device.patch | 171 ++ ...RM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch | 146 ++ ...-qcom-add-gmac-nodes-to-ipq806x-platforms.patch | 212 ++ ...orm-add-support-for-retreiving-mac-from-m.patch | 35 + .../patches-4.1/020-add-ap148-bootargs.patch | 46 + .../patches-4.1/021-add-ap148-partitions.patch | 19 + .../ipq806x/patches-4.1/022-add-db149-dts.patch | 160 ++ ...M-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch | 53 + .../patches-4.1/024-ap148-add-memory-node.patch | 14 + ...0-hwspinlock-core-add-device-tree-support.patch | 167 ++ ...qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch | 234 +++ ...spinlock-qcom-Correct-msb-in-regmap_field.patch | 26 + ...33-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch | 33 + ...soc-qcom-Add-device-tree-binding-for-SMEM.patch | 82 + ...soc-qcom-Add-Shared-Memory-Manager-driver.patch | 841 ++++++++ ...-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch | 36 + ...37-mtd-add-SMEM-parser-for-QCOM-platforms.patch | 277 +++ ...CI-qcom-Document-PCIe-devicetree-bindings.patch | 263 +++ ...-qcom-Add-Qualcomm-PCIe-controller-driver.patch | 753 ++++++++ ...-qcom-add-pcie-nodes-to-ipq806x-platforms.patch | 266 +++ ...tomatically-select-PCI_DOMAINS-if-PCI-is-.patch | 29 + .../patches-4.1/114-pcie-add-ctlr-init.patch | 278 +++ .../patches-4.1/115-add-pcie-aux-clk-dts.patch | 80 + .../patches-4.1/126-add-rpm-to-ipq8064-dts.patch | 87 + ...-Add-Krait-L2-register-accessor-functions.patch | 144 ++ ...ux-Split-out-register-accessors-for-reuse.patch | 192 ++ ...ates-to-downstream-clocks-during-set_rate.patch | 130 ++ .../patches-4.1/136-clk-Add-safe-switch-hook.patch | 164 ++ ...dd-support-for-High-Frequency-PLLs-HFPLLs.patch | 351 ++++ .../138-clk-qcom-Add-HFPLL-driver.patch | 206 ++ .../139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch | 127 ++ ...140-clk-qcom-Add-support-for-Krait-clocks.patch | 271 +++ .../141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch | 205 ++ ...lk-qcom-Add-Krait-clock-controller-driver.patch | 435 +++++ ...-module-to-register-cpufreq-on-Krait-CPUs.patch | 304 +++ ...m-Add-necessary-DT-data-for-Krait-cpufreq.patch | 100 + ...ufreq-Add-a-cpufreq-krait-based-on-cpufre.patch | 461 +++++ ...-bindings-qcom_adm-Fix-channel-specifiers.patch | 76 + .../patches-4.1/156-dmaengine-Add-ADM-driver.patch | 962 ++++++++++ .../157-ARM-DT-ipq8064-Add-ADM-device-node.patch | 42 + ...g-to-access-bad-block-markers-in-raw-mode.patch | 84 + ...-mtd-nand-Qualcomm-NAND-controller-driver.patch | 2024 ++++++++++++++++++++ ...63-dt-bindings-qcom_nandc-Add-DT-bindings.patch | 82 + ...-dts-Add-NAND-controller-node-for-ipq806x.patch | 51 + ...nable-NAND-node-on-IPQ8064-AP148-platform.patch | 76 + ...h-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch | 11 + .../300-arch-arm-force-ZRELADDR-on-arch-qcom.patch | 62 + ...dd-Netgear-Nighthawk-X4-R7500-device-tree.patch | 387 ++++ .../302-mtd-qcom-smem-rename-rootfs-ubi.patch | 13 + ...dd-support-for-NSS-GMAC-clocks-and-resets.patch | 734 +++++++ ...-phy-handle-support-to-the-platform-layer.patch | 105 + ...-error-path-at-the-end-of-stmmac_probe_co.patch | 65 + ...stmmac-add-fixed-link-device-tree-support.patch | 64 + .../704-stmmac-add-ipq806x-glue-layer.patch | 407 ++++ ...mac-ipq806x-document-device-tree-bindings.patch | 52 + ...RM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch | 146 ++ ...-qcom-add-gmac-nodes-to-ipq806x-platforms.patch | 212 ++ ...orm-add-support-for-retreiving-mac-from-m.patch | 35 + target/linux/ipq806x/profiles/default.mk | 19 + target/linux/ipq806x/profiles/netgear.mk | 20 + 148 files changed, 30301 insertions(+) create mode 100644 target/linux/ipq806x/Makefile create mode 100644 target/linux/ipq806x/base-files.mk create mode 100644 target/linux/ipq806x/base-files/etc/inittab create mode 100644 target/linux/ipq806x/base-files/etc/uci-defaults/leds create mode 100755 target/linux/ipq806x/base-files/etc/uci-defaults/network create mode 100644 target/linux/ipq806x/base-files/lib/ipq806x.sh create mode 100644 target/linux/ipq806x/base-files/lib/preinit/03_preinit_do_ipq806x.sh create mode 100644 target/linux/ipq806x/base-files/lib/upgrade/platform.sh create mode 100644 target/linux/ipq806x/config-3.18 create mode 100644 target/linux/ipq806x/config-4.1 create mode 100644 target/linux/ipq806x/image/Makefile create mode 100644 target/linux/ipq806x/modules.mk create mode 100644 target/linux/ipq806x/patches-3.18/001-spi-qup-Add-DMA-capabilities.patch create mode 100644 target/linux/ipq806x/patches-3.18/002-v3-spi-qup-Fix-incorrect-block-transfers.patch create mode 100644 target/linux/ipq806x/patches-3.18/003-spi-qup-Ensure-done-detection.patch create mode 100644 target/linux/ipq806x/patches-3.18/011-watchdog-qcom-use-timer-devicetree-binding.patch create mode 100644 target/linux/ipq806x/patches-3.18/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch create mode 100644 target/linux/ipq806x/patches-3.18/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch create mode 100644 target/linux/ipq806x/patches-3.18/020-add-ap148-bootargs.patch create mode 100644 target/linux/ipq806x/patches-3.18/021-add-ap148-partitions.patch create mode 100644 target/linux/ipq806x/patches-3.18/022-add-db149-dts.patch create mode 100644 target/linux/ipq806x/patches-3.18/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch create mode 100644 target/linux/ipq806x/patches-3.18/024-ap148-add-memory-node.patch create mode 100644 target/linux/ipq806x/patches-3.18/030-hwspinlock-core-add-device-tree-support.patch create mode 100644 target/linux/ipq806x/patches-3.18/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch create mode 100644 target/linux/ipq806x/patches-3.18/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch create mode 100644 target/linux/ipq806x/patches-3.18/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch create mode 100644 target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch create mode 100644 target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch create mode 100644 target/linux/ipq806x/patches-3.18/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch create mode 100644 target/linux/ipq806x/patches-3.18/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch create mode 100644 target/linux/ipq806x/patches-3.18/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch create mode 100644 target/linux/ipq806x/patches-3.18/102-soc-qcom-gsbi-Add-support-for-ADM-CRCI-muxing.patch create mode 100644 target/linux/ipq806x/patches-3.18/103-ARM-DT-ipq8064-Add-TCSR-support.patch create mode 100644 target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch create mode 100644 target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch create mode 100644 target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch create mode 100644 target/linux/ipq806x/patches-3.18/114-pcie-add-ctlr-init.patch create mode 100644 target/linux/ipq806x/patches-3.18/115-add-pcie-aux-clk-dts.patch create mode 100644 target/linux/ipq806x/patches-3.18/120-mfd-qcom-rpm-Driver-for-the-Qualcomm-RPM.patch create mode 100644 target/linux/ipq806x/patches-3.18/121-mfd-qcom_rpm-Add-support-for-IPQ8064.patch create mode 100644 target/linux/ipq806x/patches-3.18/122-mfd-devicetree-bindings-Add-Qualcomm-RPM-DT-binding.patch create mode 100644 target/linux/ipq806x/patches-3.18/123-mfd-devicetree-qcom_rpm-Document-IPQ8064-resources.patch create mode 100644 target/linux/ipq806x/patches-3.18/124-regulator-rpm-add-support-for-RPM-controller-SMB208.patch create mode 100644 target/linux/ipq806x/patches-3.18/125-regulator-qcom-rpm-Add-missing-state-flag-in-call-to.patch create mode 100644 target/linux/ipq806x/patches-3.18/126-add-rpm-to-ipq8064-dts.patch create mode 100644 target/linux/ipq806x/patches-3.18/130-clk_mux-Fix-set_parent-doing-the-wrong-thing-when-IN.patch create mode 100644 target/linux/ipq806x/patches-3.18/131-clk-Add-__clk_mux_determine_rate_closest.patch create mode 100644 target/linux/ipq806x/patches-3.18/132-clk-Add-clk_unregister_-divider-gate-mux-to-close-me.patch create mode 100644 target/linux/ipq806x/patches-3.18/133-ARM-Add-Krait-L2-register-accessor-functions.patch create mode 100644 target/linux/ipq806x/patches-3.18/134-clk-mux-Split-out-register-accessors-for-reuse.patch create mode 100644 target/linux/ipq806x/patches-3.18/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch create mode 100644 target/linux/ipq806x/patches-3.18/136-clk-Add-safe-switch-hook.patch create mode 100644 target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch create mode 100644 target/linux/ipq806x/patches-3.18/138-clk-qcom-Add-HFPLL-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch create mode 100644 target/linux/ipq806x/patches-3.18/140-clk-qcom-Add-support-for-Krait-clocks.patch create mode 100644 target/linux/ipq806x/patches-3.18/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/142-clk-qcom-Add-Krait-clock-controller-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch create mode 100644 target/linux/ipq806x/patches-3.18/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch create mode 100644 target/linux/ipq806x/patches-3.18/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch create mode 100644 target/linux/ipq806x/patches-3.18/150-dmaengine-Rework-dma_chan_get.patch create mode 100644 target/linux/ipq806x/patches-3.18/151-dmaengine-Remove-the-need-to-declare-device_control.patch create mode 100644 target/linux/ipq806x/patches-3.18/152-dmaengine-Make-channel-allocation-callbacks-optional.patch create mode 100644 target/linux/ipq806x/patches-3.18/153-dmaengine-Introduce-a-device_config-callback.patch create mode 100644 target/linux/ipq806x/patches-3.18/154-dmaengine-Add-device_terminate_all-callback.patch create mode 100644 target/linux/ipq806x/patches-3.18/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch create mode 100644 target/linux/ipq806x/patches-3.18/156-dmaengine-Add-ADM-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/157-ARM-DT-ipq8064-Add-ADM-device-node.patch create mode 100644 target/linux/ipq806x/patches-3.18/160-clk-qcom-Add-EBI2-clocks-for-IPQ806x.patch create mode 100644 target/linux/ipq806x/patches-3.18/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch create mode 100644 target/linux/ipq806x/patches-3.18/162-mtd-nand-Qualcomm-NAND-controller-driver.patch create mode 100644 target/linux/ipq806x/patches-3.18/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch create mode 100644 target/linux/ipq806x/patches-3.18/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch create mode 100644 target/linux/ipq806x/patches-3.18/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch create mode 100644 target/linux/ipq806x/patches-3.18/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch create mode 100644 target/linux/ipq806x/patches-3.18/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch create mode 100644 target/linux/ipq806x/patches-3.18/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch create mode 100644 target/linux/ipq806x/patches-3.18/302-mtd-qcom-smem-rename-rootfs-ubi.patch create mode 100644 target/linux/ipq806x/patches-3.18/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch create mode 100644 target/linux/ipq806x/patches-3.18/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch create mode 100644 target/linux/ipq806x/patches-3.18/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch create mode 100644 target/linux/ipq806x/patches-3.18/703-stmmac-add-fixed-link-device-tree-support.patch create mode 100644 target/linux/ipq806x/patches-3.18/704-stmmac-add-ipq806x-glue-layer.patch create mode 100644 target/linux/ipq806x/patches-3.18/705-net-stmmac-ipq806x-document-device-tree-bindings.patch create mode 100644 target/linux/ipq806x/patches-3.18/706-net-stmmac-create-one-debugfs-dir-per-net-device.patch create mode 100644 target/linux/ipq806x/patches-3.18/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch create mode 100644 target/linux/ipq806x/patches-3.18/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch create mode 100644 target/linux/ipq806x/patches-3.18/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch create mode 100644 target/linux/ipq806x/patches-4.1/020-add-ap148-bootargs.patch create mode 100644 target/linux/ipq806x/patches-4.1/021-add-ap148-partitions.patch create mode 100644 target/linux/ipq806x/patches-4.1/022-add-db149-dts.patch create mode 100644 target/linux/ipq806x/patches-4.1/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch create mode 100644 target/linux/ipq806x/patches-4.1/024-ap148-add-memory-node.patch create mode 100644 target/linux/ipq806x/patches-4.1/030-hwspinlock-core-add-device-tree-support.patch create mode 100644 target/linux/ipq806x/patches-4.1/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch create mode 100644 target/linux/ipq806x/patches-4.1/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch create mode 100644 target/linux/ipq806x/patches-4.1/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch create mode 100644 target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch create mode 100644 target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch create mode 100644 target/linux/ipq806x/patches-4.1/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch create mode 100644 target/linux/ipq806x/patches-4.1/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch create mode 100644 target/linux/ipq806x/patches-4.1/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch create mode 100644 target/linux/ipq806x/patches-4.1/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch create mode 100644 target/linux/ipq806x/patches-4.1/114-pcie-add-ctlr-init.patch create mode 100644 target/linux/ipq806x/patches-4.1/115-add-pcie-aux-clk-dts.patch create mode 100644 target/linux/ipq806x/patches-4.1/126-add-rpm-to-ipq8064-dts.patch create mode 100644 target/linux/ipq806x/patches-4.1/133-ARM-Add-Krait-L2-register-accessor-functions.patch create mode 100644 target/linux/ipq806x/patches-4.1/134-clk-mux-Split-out-register-accessors-for-reuse.patch create mode 100644 target/linux/ipq806x/patches-4.1/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch create mode 100644 target/linux/ipq806x/patches-4.1/136-clk-Add-safe-switch-hook.patch create mode 100644 target/linux/ipq806x/patches-4.1/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch create mode 100644 target/linux/ipq806x/patches-4.1/138-clk-qcom-Add-HFPLL-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch create mode 100644 target/linux/ipq806x/patches-4.1/140-clk-qcom-Add-support-for-Krait-clocks.patch create mode 100644 target/linux/ipq806x/patches-4.1/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/142-clk-qcom-Add-Krait-clock-controller-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch create mode 100644 target/linux/ipq806x/patches-4.1/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch create mode 100644 target/linux/ipq806x/patches-4.1/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch create mode 100644 target/linux/ipq806x/patches-4.1/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch create mode 100644 target/linux/ipq806x/patches-4.1/156-dmaengine-Add-ADM-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/157-ARM-DT-ipq8064-Add-ADM-device-node.patch create mode 100644 target/linux/ipq806x/patches-4.1/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch create mode 100644 target/linux/ipq806x/patches-4.1/162-mtd-nand-Qualcomm-NAND-controller-driver.patch create mode 100644 target/linux/ipq806x/patches-4.1/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch create mode 100644 target/linux/ipq806x/patches-4.1/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch create mode 100644 target/linux/ipq806x/patches-4.1/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch create mode 100644 target/linux/ipq806x/patches-4.1/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch create mode 100644 target/linux/ipq806x/patches-4.1/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch create mode 100644 target/linux/ipq806x/patches-4.1/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch create mode 100644 target/linux/ipq806x/patches-4.1/302-mtd-qcom-smem-rename-rootfs-ubi.patch create mode 100644 target/linux/ipq806x/patches-4.1/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch create mode 100644 target/linux/ipq806x/patches-4.1/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch create mode 100644 target/linux/ipq806x/patches-4.1/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch create mode 100644 target/linux/ipq806x/patches-4.1/703-stmmac-add-fixed-link-device-tree-support.patch create mode 100644 target/linux/ipq806x/patches-4.1/704-stmmac-add-ipq806x-glue-layer.patch create mode 100644 target/linux/ipq806x/patches-4.1/705-net-stmmac-ipq806x-document-device-tree-bindings.patch create mode 100644 target/linux/ipq806x/patches-4.1/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch create mode 100644 target/linux/ipq806x/patches-4.1/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch create mode 100644 target/linux/ipq806x/patches-4.1/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch create mode 100644 target/linux/ipq806x/profiles/default.mk create mode 100644 target/linux/ipq806x/profiles/netgear.mk (limited to 'target/linux/ipq806x') diff --git a/target/linux/ipq806x/Makefile b/target/linux/ipq806x/Makefile new file mode 100644 index 0000000..45f83dd --- /dev/null +++ b/target/linux/ipq806x/Makefile @@ -0,0 +1,22 @@ +# Copyright (c) 2013 The Linux Foundation. All rights reserved. +# +include $(TOPDIR)/rules.mk + +ARCH:=arm +BOARD:=ipq806x +BOARDNAME:=Qualcomm Atheros IPQ806X +FEATURES:=squashfs nand ubifs +CPU_TYPE:=cortex-a7 +MAINTAINER:=John Crispin + +KERNEL_PATCHVER:=3.18 + +KERNELNAME:=zImage Image dtbs + +include $(INCLUDE_DIR)/target.mk +DEFAULT_PACKAGES += \ + kmod-leds-gpio kmod-gpio-button-hotplug swconfig \ + kmod-ata-core kmod-ata-ahci kmod-ata-ahci-platform + + +$(eval $(call BuildTarget)) diff --git a/target/linux/ipq806x/base-files.mk b/target/linux/ipq806x/base-files.mk new file mode 100644 index 0000000..fdd2c71 --- /dev/null +++ b/target/linux/ipq806x/base-files.mk @@ -0,0 +1,3 @@ +define Package/base-files/install-target + rm -f $(1)/etc/config/network +endef diff --git a/target/linux/ipq806x/base-files/etc/inittab b/target/linux/ipq806x/base-files/etc/inittab new file mode 100644 index 0000000..19a6e11 --- /dev/null +++ b/target/linux/ipq806x/base-files/etc/inittab @@ -0,0 +1,4 @@ +# Copyright (c) 2013 The Linux Foundation. All rights reserved. +::sysinit:/etc/init.d/rcS S boot +::shutdown:/etc/init.d/rcS K shutdown +ttyMSM0::askfirst:/bin/ash --login diff --git a/target/linux/ipq806x/base-files/etc/uci-defaults/leds b/target/linux/ipq806x/base-files/etc/uci-defaults/leds new file mode 100644 index 0000000..362c5b6 --- /dev/null +++ b/target/linux/ipq806x/base-files/etc/uci-defaults/leds @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (C) 2015 OpenWrt.org +# + +. /lib/functions/uci-defaults.sh +. /lib/ipq806x.sh + +board=$(ipq806x_board_name) + +case "$board" in +r7500) + ucidef_set_led_usbdev "usb1" "USB 1" "r7500:white:usb1" "1-1" + ucidef_set_led_usbdev "usb2" "USB 2" "r7500:white:usb3" "3-1" + ucidef_set_led_netdev "wan" "WAN" "r7500:white:wan" "eth0" + ucidef_set_led_ide_disk "esata" "eSATA" "r7500:amber:esata" + ucidef_set_led_default "wps" "WPS" "r7500:white:wps" "0" + ucidef_set_led_default "rfkill" "rfkill" "r7500:white:rfkill" "0" + ;; +*) + ;; +esac + +ucidef_commit_leds + +exit 0 diff --git a/target/linux/ipq806x/base-files/etc/uci-defaults/network b/target/linux/ipq806x/base-files/etc/uci-defaults/network new file mode 100755 index 0000000..7742b73 --- /dev/null +++ b/target/linux/ipq806x/base-files/etc/uci-defaults/network @@ -0,0 +1,39 @@ +#!/bin/sh +# +# Copyright (c) 2015 The Linux Foundation. All rights reserved. +# Copyright (C) 2011 OpenWrt.org +# + +[ -e /etc/config/network ] && exit 0 + +touch /etc/config/network + +. /lib/functions/uci-defaults.sh +. /lib/ipq806x.sh + +ucidef_set_interface_loopback + +board=$(ipq806x_board_name) + +case "$board" in +ap148 |\ +r7500) + ucidef_set_interfaces_lan_wan "eth1" "eth0" + ucidef_add_switch "switch0" "1" "1" + ucidef_add_switch_vlan "switch0" "1" "6 1 2 3 4" + ucidef_add_switch_vlan "switch0" "2" "0 5" + ;; +db149) + ucidef_set_interfaces_lan_wan "eth1 eth2 eth3" "eth0" + ucidef_add_switch "switch0" "1" "1" + ucidef_add_switch_vlan "switch0" "1" "6 1 2 3 4" + ucidef_add_switch_vlan "switch0" "2" "0 5" + ;; +*) + echo "Unsupported hardware. Network interfaces not intialized" + ;; +esac + +uci commit network + +exit 0 diff --git a/target/linux/ipq806x/base-files/lib/ipq806x.sh b/target/linux/ipq806x/base-files/lib/ipq806x.sh new file mode 100644 index 0000000..5b27bde --- /dev/null +++ b/target/linux/ipq806x/base-files/lib/ipq806x.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# Copyright (C) 2011 OpenWrt.org +# + +IPQ806X_BOARD_NAME= +IPQ806X_MODEL= + +ipq806x_board_detect() { + local machine + local name + + machine=$(cat /proc/device-tree/model) + + case "$machine" in + *"AP148") + name="ap148" + ;; + *"DB149") + name="db149" + ;; + *"R7500") + name="r7500" + ;; + esac + + [ -z "$name" ] && name="unknown" + + [ -z "$IPQ806X_BOARD_NAME" ] && IPQ806X_BOARD_NAME="$name" + [ -z "$IPQ806X_MODEL" ] && IPQ806X_MODEL="$machine" + + [ -e "/tmp/sysinfo/" ] || mkdir -p "/tmp/sysinfo/" + + echo "$IPQ806X_BOARD_NAME" > /tmp/sysinfo/board_name + echo "$IPQ806X_MODEL" > /tmp/sysinfo/model +} + +ipq806x_board_name() { + local name + + [ -f /tmp/sysinfo/board_name ] && name=$(cat /tmp/sysinfo/board_name) + [ -z "$name" ] && name="unknown" + + echo "$name" +} diff --git a/target/linux/ipq806x/base-files/lib/preinit/03_preinit_do_ipq806x.sh b/target/linux/ipq806x/base-files/lib/preinit/03_preinit_do_ipq806x.sh new file mode 100644 index 0000000..785f1eb --- /dev/null +++ b/target/linux/ipq806x/base-files/lib/preinit/03_preinit_do_ipq806x.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# + +do_ipq806x() { + . /lib/ipq806x.sh + + ipq806x_board_detect +} + +boot_hook_add preinit_main do_ipq806x diff --git a/target/linux/ipq806x/base-files/lib/upgrade/platform.sh b/target/linux/ipq806x/base-files/lib/upgrade/platform.sh new file mode 100644 index 0000000..c0e19a1 --- /dev/null +++ b/target/linux/ipq806x/base-files/lib/upgrade/platform.sh @@ -0,0 +1,30 @@ +. /lib/ipq806x.sh + +PART_NAME=firmware + +platform_check_image() { + local board=$(ipq806x_board_name) + + case "$board" in + AP148 |\ + r7500) + nand_do_platform_check $board $1 + return $?; + ;; + *) + return 1; + esac +} + +platform_pre_upgrade() { + local board=$(ipq806x_board_name) + + case "$board" in + AP148 |\ + r7500) + nand_do_upgrade "$1" + ;; + esac +} + +# use default for platform_do_upgrade() diff --git a/target/linux/ipq806x/config-3.18 b/target/linux/ipq806x/config-3.18 new file mode 100644 index 0000000..cb3dcfc --- /dev/null +++ b/target/linux/ipq806x/config-3.18 @@ -0,0 +1,444 @@ +CONFIG_ALIGNMENT_TRAP=y +# CONFIG_AMBA_PL08X is not set +# CONFIG_APM_EMULATION is not set +CONFIG_APQ_GCC_8084=y +CONFIG_APQ_MMCC_8084=y +CONFIG_AR8216_PHY=y +CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE=y +CONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE=y +CONFIG_ARCH_HAS_SG_CHAIN=y +CONFIG_ARCH_HAS_TICK_BROADCAST=y +CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y +CONFIG_ARCH_MSM8960=y +CONFIG_ARCH_MSM8974=y +CONFIG_ARCH_MSM8X60=y +CONFIG_ARCH_MULTIPLATFORM=y +# CONFIG_ARCH_MULTI_CPU_AUTO is not set +CONFIG_ARCH_MULTI_V6_V7=y +CONFIG_ARCH_MULTI_V7=y +CONFIG_ARCH_NR_GPIO=0 +CONFIG_ARCH_QCOM=y +CONFIG_ARCH_REQUIRE_GPIOLIB=y +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARCH_USE_BUILTIN_BSWAP=y +CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y +CONFIG_ARCH_WANT_GENERAL_HUGETLB=y +CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y +CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y +CONFIG_ARM=y +CONFIG_ARM_AMBA=y +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +# CONFIG_ARM_ATAG_DTB_COMPAT is not set +CONFIG_ARM_CPU_SUSPEND=y +CONFIG_ARM_GIC=y +CONFIG_ARM_HAS_SG_CHAIN=y +# CONFIG_ARM_KIRKWOOD_CPUFREQ is not set +CONFIG_ARM_L1_CACHE_SHIFT=6 +CONFIG_ARM_L1_CACHE_SHIFT_6=y +# CONFIG_ARM_LPAE is not set +CONFIG_ARM_PATCH_PHYS_VIRT=y +CONFIG_ARM_QCOM_CPUFREQ=y +# CONFIG_ARM_SP805_WATCHDOG is not set +CONFIG_ARM_THUMB=y +# CONFIG_ARM_THUMBEE is not set +CONFIG_ARM_UNWIND=y +CONFIG_ARM_VIRT_EXT=y +CONFIG_AT803X_PHY=y +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BOUNCE=y +CONFIG_BUILD_BIN2C=y +# CONFIG_CACHE_L2X0 is not set +CONFIG_CLEANCACHE=y +CONFIG_CLKDEV_LOOKUP=y +CONFIG_CLKSRC_OF=y +CONFIG_CLKSRC_QCOM=y +CONFIG_CLONE_BACKWARDS=y +CONFIG_COMMON_CLK=y +CONFIG_COMMON_CLK_QCOM=y +CONFIG_COMPACTION=y +CONFIG_COREDUMP=y +# CONFIG_CPUFREQ_DT is not set +CONFIG_CPU_32v6K=y +CONFIG_CPU_32v7=y +CONFIG_CPU_ABRT_EV7=y +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_CPU_CACHE_V7=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y +CONFIG_CPU_FREQ=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set +# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_HAS_ASID=y +# CONFIG_CPU_ICACHE_DISABLE is not set +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_CPU_PABRT_V7=y +CONFIG_CPU_PM=y +CONFIG_CPU_RMAP=y +# CONFIG_CPU_THERMAL is not set +CONFIG_CPU_TLB_V7=y +CONFIG_CPU_V7=y +CONFIG_CRC16=y +# CONFIG_CRC32_SARWATE is not set +CONFIG_CRC32_SLICEBY8=y +CONFIG_CROSS_MEMORY_ATTACH=y +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y +# CONFIG_CRYPTO_SHA1_ARM_NEON is not set +# CONFIG_CRYPTO_SHA512_ARM_NEON is not set +CONFIG_CRYPTO_XZ=y +CONFIG_DCACHE_WORD_ACCESS=y +CONFIG_DEBUG_BUGVERBOSE=y +CONFIG_DEBUG_GPIO=y +CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_UART_8250 is not set +# CONFIG_DEBUG_UART_PL01X is not set +# CONFIG_DEBUG_USER is not set +CONFIG_DECOMPRESS_GZIP=y +CONFIG_DMADEVICES=y +CONFIG_DMA_ENGINE=y +CONFIG_DMA_OF=y +CONFIG_DMA_VIRTUAL_CHANNELS=y +CONFIG_DTC=y +CONFIG_DWMAC_IPQ806X=y +# CONFIG_DW_DMAC_CORE is not set +# CONFIG_DW_DMAC_PCI is not set +CONFIG_DYNAMIC_DEBUG=y +CONFIG_ETHERNET_PACKET_MANGLE=y +CONFIG_FIXED_PHY=y +CONFIG_FREEZER=y +CONFIG_GENERIC_ALLOCATOR=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_GENERIC_CLOCKEVENTS_BUILD=y +CONFIG_GENERIC_CPUFREQ_KRAIT=y +CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_GENERIC_IO=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_GENERIC_PHY=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_GPIOLIB=y +CONFIG_GPIOLIB_IRQCHIP=y +CONFIG_GPIO_DEVRES=y +# CONFIG_GPIO_MSM_V2 is not set +CONFIG_GPIO_SYSFS=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_HAVE_64BIT_ALIGNED_ACCESS is not set +CONFIG_HAVE_ARCH_AUDITSYSCALL=y +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_HAVE_ARCH_KGDB=y +CONFIG_HAVE_ARCH_PFN_VALID=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_ARM_ARCH_TIMER=y +# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set +CONFIG_HAVE_BPF_JIT=y +CONFIG_HAVE_CC_STACKPROTECTOR=y +CONFIG_HAVE_CLK=y +CONFIG_HAVE_CLK_PREPARE=y +CONFIG_HAVE_CONTEXT_TRACKING=y +CONFIG_HAVE_C_RECORDMCOUNT=y +CONFIG_HAVE_DEBUG_KMEMLEAK=y +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DMA_CONTIGUOUS=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_IDE=y +CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_HAVE_KERNEL_LZ4=y +CONFIG_HAVE_KERNEL_LZMA=y +CONFIG_HAVE_KERNEL_LZO=y +CONFIG_HAVE_KERNEL_XZ=y +CONFIG_HAVE_MEMBLOCK=y +CONFIG_HAVE_MOD_ARCH_SPECIFIC=y +CONFIG_HAVE_NET_DSA=y +CONFIG_HAVE_OPROFILE=y +CONFIG_HAVE_PERF_EVENTS=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_PROC_CPU=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_SMP=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_UID16=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_HIGHMEM=y +CONFIG_HIGHPTE=y +CONFIG_HOTPLUG_CPU=y +CONFIG_HWMON=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_QCOM=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM=y +CONFIG_HZ_FIXED=0 +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_QUP=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_IOMMU_API=y +CONFIG_IOMMU_HELPER=y +CONFIG_IOMMU_PGTABLES_L2=y +CONFIG_IOMMU_SUPPORT=y +CONFIG_IPQ_GCC_806X=y +CONFIG_IRQCHIP=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_IRQ_WORK=y +CONFIG_KPSS_XCC=y +CONFIG_KRAITCC=y +CONFIG_KRAIT_CLOCKS=y +CONFIG_KRAIT_L2_ACCESSORS=y +# CONFIG_LEDS_REGULATOR is not set +CONFIG_LEDS_TRIGGER_IDE_DISK=y +CONFIG_LIBFDT=y +CONFIG_LOCKUP_DETECTOR=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_BOARDINFO=y +CONFIG_MDIO_GPIO=y +CONFIG_MFD_QCOM_RPM=y +# CONFIG_MFD_SPMI_PMIC is not set +CONFIG_MFD_SYSCON=y +CONFIG_MIGHT_HAVE_CACHE_L2X0=y +CONFIG_MIGHT_HAVE_PCI=y +CONFIG_MIGRATION=y +CONFIG_MODULES_USE_ELF_REL=y +CONFIG_MSM_GCC_8660=y +CONFIG_MSM_GCC_8960=y +CONFIG_MSM_GCC_8974=y +CONFIG_MSM_IOMMU=y +CONFIG_MSM_MMCC_8960=y +CONFIG_MSM_MMCC_8974=y +CONFIG_MTD_CMDLINE_PARTS=y +# CONFIG_MTD_IMPA7 is not set +CONFIG_MTD_JEDECPROBE=y +CONFIG_MTD_M25P80=y +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_ECC=y +CONFIG_MTD_NAND_QCOM=y +CONFIG_MTD_QCOM_SMEM_PARTS=y +CONFIG_MTD_SPI_NOR=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_FIT_FW=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BEB_LIMIT=20 +CONFIG_MTD_UBI_BLOCK=y +# CONFIG_MTD_UBI_FASTMAP is not set +# CONFIG_MTD_UBI_GLUEBI is not set +CONFIG_MTD_UBI_WL_THRESHOLD=4096 +CONFIG_MULTI_IRQ_HANDLER=y +CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_NEON=y +CONFIG_NET_FLOW_LIMIT=y +CONFIG_NET_PTP_CLASSIFY=y +CONFIG_NET_VENDOR_WIZNET=y +CONFIG_NO_BOOTMEM=y +CONFIG_NO_HZ=y +CONFIG_NO_HZ_COMMON=y +CONFIG_NO_HZ_IDLE=y +CONFIG_NR_CPUS=4 +CONFIG_OF=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_ADDRESS_PCI=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_FLATTREE=y +CONFIG_OF_GPIO=y +CONFIG_OF_IOMMU=y +CONFIG_OF_IRQ=y +CONFIG_OF_MDIO=y +CONFIG_OF_MTD=y +CONFIG_OF_NET=y +CONFIG_OF_PCI=y +CONFIG_OF_PCI_IRQ=y +CONFIG_OF_RESERVED_MEM=y +CONFIG_OLD_SIGACTION=y +CONFIG_OLD_SIGSUSPEND3=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PCI=y +CONFIG_PCIEAER=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIE_DW=y +CONFIG_PCIE_QCOM=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_MSI=y +CONFIG_PERF_EVENTS=y +CONFIG_PERF_USE_VMALLOC=y +CONFIG_PHYLIB=y +# CONFIG_PHY_QCOM_APQ8064_SATA is not set +CONFIG_PHY_QCOM_IPQ806X_SATA=y +CONFIG_PINCTRL=y +CONFIG_PINCTRL_APQ8064=y +# CONFIG_PINCTRL_APQ8084 is not set +CONFIG_PINCTRL_IPQ8064=y +CONFIG_PINCTRL_MSM=y +# CONFIG_PINCTRL_MSM8960 is not set +CONFIG_PINCTRL_MSM8X74=y +# CONFIG_PL330_DMA is not set +CONFIG_PM=y +CONFIG_PM_CLK=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_OPP=y +CONFIG_PM_SLEEP=y +CONFIG_PM_SLEEP_SMP=y +CONFIG_POWER_RESET=y +# CONFIG_POWER_RESET_BRCMSTB is not set +# CONFIG_POWER_RESET_GPIO is not set +# CONFIG_POWER_RESET_GPIO_RESTART is not set +# CONFIG_POWER_RESET_LTC2952 is not set +CONFIG_POWER_RESET_MSM=y +# CONFIG_POWER_RESET_SYSCON is not set +CONFIG_POWER_SUPPLY=y +CONFIG_PPS=y +CONFIG_PREEMPT=y +CONFIG_PREEMPT_COUNT=y +# CONFIG_PREEMPT_NONE is not set +CONFIG_PREEMPT_RCU=y +CONFIG_PRINTK_TIME=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_PTP_1588_CLOCK=y +CONFIG_QCOM_ADM=y +CONFIG_QCOM_BAM_DMA=y +CONFIG_QCOM_GSBI=y +CONFIG_QCOM_HFPLL=y +CONFIG_QCOM_SCM=y +CONFIG_QCOM_SMEM=y +CONFIG_QCOM_WDT=y +CONFIG_RAS=y +# CONFIG_RCU_BOOST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=21 +CONFIG_RCU_CPU_STALL_VERBOSE=y +CONFIG_RCU_STALL_COMMON=y +CONFIG_RD_GZIP=y +CONFIG_REGMAP=y +CONFIG_REGMAP_MMIO=y +CONFIG_REGULATOR=y +# CONFIG_REGULATOR_DEBUG is not set +CONFIG_REGULATOR_QCOM_RPM=y +# CONFIG_REGULATOR_USERSPACE_CONSUMER is not set +CONFIG_RESET_CONTROLLER=y +CONFIG_RFS_ACCEL=y +CONFIG_RPS=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_CMOS is not set +CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +CONFIG_SCHED_HRTICK=y +# CONFIG_SCSI_DMA is not set +# CONFIG_SERIAL_AMBA_PL010 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +CONFIG_SLUB_CPU_PARTIAL=y +CONFIG_SMP=y +CONFIG_SMP_ON_UP=y +CONFIG_SPARSE_IRQ=y +CONFIG_SPI=y +CONFIG_SPI_MASTER=y +CONFIG_SPI_QUP=y +CONFIG_SPMI=y +CONFIG_SPMI_MSM_PMIC_ARB=y +# CONFIG_STMMAC_DA is not set +CONFIG_STMMAC_DEBUG_FS=y +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_PLATFORM=y +CONFIG_STOP_MACHINE=y +# CONFIG_STRIP_ASM_SYMS is not set +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +CONFIG_SWCONFIG=y +CONFIG_SWIOTLB=y +CONFIG_SWP_EMULATE=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_THERMAL=y +# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set +# CONFIG_THERMAL_EMULATION is not set +# CONFIG_THERMAL_GOV_FAIR_SHARE is not set +CONFIG_THERMAL_GOV_STEP_WISE=y +# CONFIG_THERMAL_GOV_USER_SPACE is not set +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_OF=y +# CONFIG_THUMB2_KERNEL is not set +CONFIG_TICK_CPU_ACCOUNTING=y +CONFIG_TIMER_STATS=y +CONFIG_TREE_PREEMPT_RCU=y +CONFIG_UBIFS_FS=y +# CONFIG_UBIFS_FS_ADVANCED_COMPR is not set +CONFIG_UBIFS_FS_LZO=y +CONFIG_UBIFS_FS_XZ=y +CONFIG_UBIFS_FS_ZLIB=y +CONFIG_UEVENT_HELPER_PATH="" +CONFIG_UID16=y +CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" +CONFIG_UNINLINE_SPIN_UNLOCK=y +CONFIG_USB_SUPPORT=y +CONFIG_USE_OF=y +CONFIG_VECTORS_BASE=0xffff0000 +# CONFIG_VFIO is not set +CONFIG_VFP=y +CONFIG_VFPv3=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_WATCHDOG_CORE=y +# CONFIG_WIZNET_W5100 is not set +# CONFIG_WIZNET_W5300 is not set +# CONFIG_WL_TI is not set +# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set +CONFIG_XPS=y +CONFIG_XZ_DEC_ARM=y +CONFIG_XZ_DEC_BCJ=y +CONFIG_ZBOOT_ROM_BSS=0 +CONFIG_ZBOOT_ROM_TEXT=0 +CONFIG_ZLIB_DEFLATE=y +CONFIG_ZLIB_INFLATE=y +CONFIG_ZONE_DMA_FLAG=0 diff --git a/target/linux/ipq806x/config-4.1 b/target/linux/ipq806x/config-4.1 new file mode 100644 index 0000000..4ead455 --- /dev/null +++ b/target/linux/ipq806x/config-4.1 @@ -0,0 +1,449 @@ +CONFIG_ALIGNMENT_TRAP=y +# CONFIG_AMBA_PL08X is not set +# CONFIG_APM_EMULATION is not set +CONFIG_APQ_GCC_8084=y +CONFIG_APQ_MMCC_8084=y +CONFIG_AR8216_PHY=y +CONFIG_ARCH_HAS_ATOMIC64_DEC_IF_POSITIVE=y +CONFIG_ARCH_HAS_ELF_RANDOMIZE=y +CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y +CONFIG_ARCH_HAS_SG_CHAIN=y +CONFIG_ARCH_HAS_TICK_BROADCAST=y +CONFIG_ARCH_HAVE_CUSTOM_GPIO_H=y +CONFIG_ARCH_HIBERNATION_POSSIBLE=y +CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y +CONFIG_ARCH_MSM8960=y +CONFIG_ARCH_MSM8974=y +CONFIG_ARCH_MSM8X60=y +CONFIG_ARCH_MULTIPLATFORM=y +# CONFIG_ARCH_MULTI_CPU_AUTO is not set +CONFIG_ARCH_MULTI_V6_V7=y +CONFIG_ARCH_MULTI_V7=y +CONFIG_ARCH_NR_GPIO=0 +CONFIG_ARCH_QCOM=y +# CONFIG_ARCH_SELECT_MEMORY_MODEL is not set +# CONFIG_ARCH_SPARSEMEM_DEFAULT is not set +CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y +CONFIG_ARCH_SUPPORTS_BIG_ENDIAN=y +CONFIG_ARCH_SUPPORTS_UPROBES=y +CONFIG_ARCH_SUSPEND_POSSIBLE=y +CONFIG_ARCH_USE_BUILTIN_BSWAP=y +CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y +CONFIG_ARCH_WANT_GENERAL_HUGETLB=y +CONFIG_ARCH_WANT_IPC_PARSE_VERSION=y +CONFIG_ARCH_WANT_OPTIONAL_GPIOLIB=y +CONFIG_ARM=y +CONFIG_ARM_AMBA=y +CONFIG_ARM_APPENDED_DTB=y +CONFIG_ARM_ARCH_TIMER=y +CONFIG_ARM_ARCH_TIMER_EVTSTREAM=y +# CONFIG_ARM_ATAG_DTB_COMPAT is not set +CONFIG_ARM_CCI=y +CONFIG_ARM_CCI400_COMMON=y +CONFIG_ARM_CCI400_PMU=y +# CONFIG_ARM_CPUIDLE is not set +CONFIG_ARM_CPU_SUSPEND=y +CONFIG_ARM_GIC=y +CONFIG_ARM_HAS_SG_CHAIN=y +CONFIG_ARM_L1_CACHE_SHIFT=6 +CONFIG_ARM_L1_CACHE_SHIFT_6=y +# CONFIG_ARM_LPAE is not set +CONFIG_ARM_PATCH_PHYS_VIRT=y +CONFIG_ARM_QCOM_CPUFREQ=y +# CONFIG_ARM_SMMU is not set +# CONFIG_ARM_SP805_WATCHDOG is not set +CONFIG_ARM_THUMB=y +# CONFIG_ARM_THUMBEE is not set +CONFIG_ARM_UNWIND=y +CONFIG_ARM_VIRT_EXT=y +CONFIG_AT803X_PHY=y +# CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC is not set +CONFIG_BOOTPARAM_SOFTLOCKUP_PANIC_VALUE=0 +CONFIG_BOUNCE=y +CONFIG_BUILD_BIN2C=y +# CONFIG_CACHE_L2X0 is not set +CONFIG_CLEANCACHE=y +CONFIG_CLKDEV_LOOKUP=y +CONFIG_CLKSRC_OF=y +CONFIG_CLKSRC_QCOM=y +CONFIG_CLONE_BACKWARDS=y +CONFIG_COMMON_CLK=y +CONFIG_COMMON_CLK_QCOM=y +CONFIG_COMPACTION=y +CONFIG_COREDUMP=y +# CONFIG_CPUFREQ_DT is not set +CONFIG_CPU_32v6K=y +CONFIG_CPU_32v7=y +CONFIG_CPU_ABRT_EV7=y +# CONFIG_CPU_BIG_ENDIAN is not set +# CONFIG_CPU_BPREDICT_DISABLE is not set +CONFIG_CPU_CACHE_V7=y +CONFIG_CPU_CACHE_VIPT=y +CONFIG_CPU_COPY_V6=y +CONFIG_CPU_CP15=y +CONFIG_CPU_CP15_MMU=y +CONFIG_CPU_FREQ=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND is not set +CONFIG_CPU_FREQ_DEFAULT_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_DEFAULT_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE is not set +# CONFIG_CPU_FREQ_GOV_CONSERVATIVE is not set +# CONFIG_CPU_FREQ_GOV_ONDEMAND is not set +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +# CONFIG_CPU_FREQ_GOV_POWERSAVE is not set +# CONFIG_CPU_FREQ_GOV_USERSPACE is not set +CONFIG_CPU_FREQ_STAT=y +# CONFIG_CPU_FREQ_STAT_DETAILS is not set +CONFIG_CPU_HAS_ASID=y +# CONFIG_CPU_ICACHE_DISABLE is not set +CONFIG_CPU_IDLE=y +CONFIG_CPU_IDLE_GOV_LADDER=y +CONFIG_CPU_IDLE_GOV_MENU=y +CONFIG_CPU_PABRT_V7=y +CONFIG_CPU_PM=y +CONFIG_CPU_RMAP=y +# CONFIG_CPU_THERMAL is not set +CONFIG_CPU_TLB_V7=y +CONFIG_CPU_V7=y +CONFIG_CRC16=y +# CONFIG_CRC32_SARWATE is not set +CONFIG_CRC32_SLICEBY8=y +CONFIG_CROSS_MEMORY_ATTACH=y +CONFIG_CRYPTO_DEFLATE=y +CONFIG_CRYPTO_LZO=y +CONFIG_CRYPTO_XZ=y +CONFIG_DCACHE_WORD_ACCESS=y +CONFIG_DEBUG_BUGVERBOSE=y +CONFIG_DEBUG_GPIO=y +CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" +CONFIG_DEBUG_PREEMPT=y +# CONFIG_DEBUG_UART_8250 is not set +# CONFIG_DEBUG_USER is not set +CONFIG_DECOMPRESS_GZIP=y +CONFIG_DEVMEM=y +CONFIG_DMADEVICES=y +CONFIG_DMA_ENGINE=y +CONFIG_DMA_OF=y +CONFIG_DMA_VIRTUAL_CHANNELS=y +CONFIG_DTC=y +# CONFIG_DW_DMAC_PCI is not set +CONFIG_DYNAMIC_DEBUG=y +CONFIG_ETHERNET_PACKET_MANGLE=y +CONFIG_FIXED_PHY=y +CONFIG_FREEZER=y +CONFIG_GENERIC_ALLOCATOR=y +CONFIG_GENERIC_BUG=y +CONFIG_GENERIC_CLOCKEVENTS=y +CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y +CONFIG_GENERIC_CPUFREQ_KRAIT=y +CONFIG_GENERIC_IDLE_POLL_SETUP=y +CONFIG_GENERIC_IO=y +CONFIG_GENERIC_IRQ_SHOW=y +CONFIG_GENERIC_IRQ_SHOW_LEVEL=y +CONFIG_GENERIC_MSI_IRQ=y +CONFIG_GENERIC_PCI_IOMAP=y +CONFIG_GENERIC_PHY=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_GENERIC_TIME_VSYSCALL=y +CONFIG_GPIOLIB=y +CONFIG_GPIOLIB_IRQCHIP=y +CONFIG_GPIO_DEVRES=y +# CONFIG_GPIO_MSM_V2 is not set +CONFIG_GPIO_SYSFS=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_HAVE_64BIT_ALIGNED_ACCESS is not set +CONFIG_HAVE_ARCH_AUDITSYSCALL=y +CONFIG_HAVE_ARCH_BITREVERSE=y +CONFIG_HAVE_ARCH_JUMP_LABEL=y +CONFIG_HAVE_ARCH_KGDB=y +CONFIG_HAVE_ARCH_PFN_VALID=y +CONFIG_HAVE_ARCH_SECCOMP_FILTER=y +CONFIG_HAVE_ARCH_TRACEHOOK=y +CONFIG_HAVE_ARM_ARCH_TIMER=y +# CONFIG_HAVE_BOOTMEM_INFO_NODE is not set +CONFIG_HAVE_BPF_JIT=y +CONFIG_HAVE_CC_STACKPROTECTOR=y +CONFIG_HAVE_CLK=y +CONFIG_HAVE_CLK_PREPARE=y +CONFIG_HAVE_CONTEXT_TRACKING=y +CONFIG_HAVE_C_RECORDMCOUNT=y +CONFIG_HAVE_DEBUG_KMEMLEAK=y +CONFIG_HAVE_DMA_API_DEBUG=y +CONFIG_HAVE_DMA_ATTRS=y +CONFIG_HAVE_DMA_CONTIGUOUS=y +CONFIG_HAVE_DYNAMIC_FTRACE=y +CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y +CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y +CONFIG_HAVE_FUNCTION_GRAPH_TRACER=y +CONFIG_HAVE_FUNCTION_TRACER=y +CONFIG_HAVE_GENERIC_DMA_COHERENT=y +CONFIG_HAVE_HW_BREAKPOINT=y +CONFIG_HAVE_IDE=y +CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y +CONFIG_HAVE_KERNEL_GZIP=y +CONFIG_HAVE_KERNEL_LZ4=y +CONFIG_HAVE_KERNEL_LZMA=y +CONFIG_HAVE_KERNEL_LZO=y +CONFIG_HAVE_KERNEL_XZ=y +CONFIG_HAVE_MEMBLOCK=y +CONFIG_HAVE_MOD_ARCH_SPECIFIC=y +CONFIG_HAVE_NET_DSA=y +CONFIG_HAVE_OPROFILE=y +CONFIG_HAVE_OPTPROBES=y +CONFIG_HAVE_PERF_EVENTS=y +CONFIG_HAVE_PERF_REGS=y +CONFIG_HAVE_PERF_USER_STACK_DUMP=y +CONFIG_HAVE_PROC_CPU=y +CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y +CONFIG_HAVE_SMP=y +CONFIG_HAVE_SYSCALL_TRACEPOINTS=y +CONFIG_HAVE_UID16=y +CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y +CONFIG_HIGHMEM=y +CONFIG_HIGHPTE=y +CONFIG_HOTPLUG_CPU=y +# CONFIG_HSU_DMA_PCI is not set +CONFIG_HWMON=y +CONFIG_HWSPINLOCK=y +CONFIG_HWSPINLOCK_QCOM=y +CONFIG_HW_RANDOM=y +CONFIG_HW_RANDOM_MSM=y +CONFIG_HZ_FIXED=0 +CONFIG_I2C=y +CONFIG_I2C_BOARDINFO=y +CONFIG_I2C_CHARDEV=y +CONFIG_I2C_COMPAT=y +CONFIG_I2C_HELPER_AUTO=y +CONFIG_I2C_QUP=y +CONFIG_IKCONFIG=y +CONFIG_IKCONFIG_PROC=y +CONFIG_INITRAMFS_SOURCE="" +CONFIG_IOMMU_HELPER=y +# CONFIG_IOMMU_IO_PGTABLE_LPAE is not set +CONFIG_IOMMU_SUPPORT=y +CONFIG_IPQ_GCC_806X=y +# CONFIG_IPQ_LCC_806X is not set +CONFIG_IRQCHIP=y +CONFIG_IRQ_DOMAIN=y +CONFIG_IRQ_DOMAIN_HIERARCHY=y +CONFIG_IRQ_FORCED_THREADING=y +CONFIG_IRQ_WORK=y +CONFIG_KPSS_XCC=y +CONFIG_KRAITCC=y +CONFIG_KRAIT_CLOCKS=y +CONFIG_KRAIT_L2_ACCESSORS=y +CONFIG_LEDS_TRIGGER_IDE_DISK=y +CONFIG_LIBFDT=y +CONFIG_LOCKUP_DETECTOR=y +CONFIG_LOCK_SPIN_ON_OWNER=y +CONFIG_LZO_COMPRESS=y +CONFIG_LZO_DECOMPRESS=y +CONFIG_MDIO_BITBANG=y +CONFIG_MDIO_BOARDINFO=y +CONFIG_MDIO_GPIO=y +CONFIG_MFD_QCOM_RPM=y +# CONFIG_MFD_SPMI_PMIC is not set +CONFIG_MFD_SYSCON=y +CONFIG_MIGHT_HAVE_CACHE_L2X0=y +CONFIG_MIGHT_HAVE_PCI=y +CONFIG_MIGRATION=y +CONFIG_MODULES_USE_ELF_REL=y +CONFIG_MSM_GCC_8660=y +# CONFIG_MSM_GCC_8916 is not set +CONFIG_MSM_GCC_8960=y +CONFIG_MSM_GCC_8974=y +# CONFIG_MSM_LCC_8960 is not set +CONFIG_MSM_MMCC_8960=y +CONFIG_MSM_MMCC_8974=y +CONFIG_MTD_CMDLINE_PARTS=y +CONFIG_MTD_M25P80=y +CONFIG_MTD_NAND=y +CONFIG_MTD_NAND_ECC=y +CONFIG_MTD_NAND_QCOM=y +CONFIG_MTD_QCOM_SMEM_PARTS=y +CONFIG_MTD_SPI_NOR=y +CONFIG_MTD_SPLIT_FIRMWARE=y +CONFIG_MTD_SPLIT_FIT_FW=y +CONFIG_MTD_UBI=y +CONFIG_MTD_UBI_BEB_LIMIT=20 +CONFIG_MTD_UBI_BLOCK=y +# CONFIG_MTD_UBI_FASTMAP is not set +# CONFIG_MTD_UBI_GLUEBI is not set +CONFIG_MTD_UBI_WL_THRESHOLD=4096 +CONFIG_MULTI_IRQ_HANDLER=y +CONFIG_MUTEX_SPIN_ON_OWNER=y +CONFIG_NEED_DMA_MAP_STATE=y +CONFIG_NEON=y +CONFIG_NET_FLOW_LIMIT=y +CONFIG_NET_PTP_CLASSIFY=y +CONFIG_NET_VENDOR_WIZNET=y +CONFIG_NO_BOOTMEM=y +CONFIG_NO_HZ=y +CONFIG_NO_HZ_COMMON=y +CONFIG_NO_HZ_IDLE=y +CONFIG_NR_CPUS=4 +CONFIG_OF=y +CONFIG_OF_ADDRESS=y +CONFIG_OF_ADDRESS_PCI=y +CONFIG_OF_EARLY_FLATTREE=y +CONFIG_OF_FLATTREE=y +CONFIG_OF_GPIO=y +CONFIG_OF_IRQ=y +CONFIG_OF_MDIO=y +CONFIG_OF_MTD=y +CONFIG_OF_NET=y +CONFIG_OF_PCI=y +CONFIG_OF_PCI_IRQ=y +CONFIG_OF_RESERVED_MEM=y +CONFIG_OLD_SIGACTION=y +CONFIG_OLD_SIGSUSPEND3=y +CONFIG_PAGEFLAGS_EXTENDED=y +CONFIG_PAGE_OFFSET=0xC0000000 +CONFIG_PCI=y +CONFIG_PCIEAER=y +CONFIG_PCIEPORTBUS=y +CONFIG_PCIE_DW=y +# CONFIG_PCIE_IPROC is not set +CONFIG_PCIE_PME=y +CONFIG_PCIE_QCOM=y +CONFIG_PCI_DEBUG=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_DOMAINS_GENERIC=y +CONFIG_PCI_MSI=y +CONFIG_PERF_EVENTS=y +CONFIG_PERF_USE_VMALLOC=y +CONFIG_PGTABLE_LEVELS=2 +CONFIG_PHYLIB=y +# CONFIG_PHY_QCOM_APQ8064_SATA is not set +CONFIG_PHY_QCOM_IPQ806X_SATA=y +# CONFIG_PHY_QCOM_UFS is not set +CONFIG_PINCTRL=y +CONFIG_PINCTRL_APQ8064=y +# CONFIG_PINCTRL_APQ8084 is not set +CONFIG_PINCTRL_IPQ8064=y +CONFIG_PINCTRL_MSM=y +# CONFIG_PINCTRL_MSM8916 is not set +# CONFIG_PINCTRL_MSM8960 is not set +CONFIG_PINCTRL_MSM8X74=y +# CONFIG_PINCTRL_QCOM_SPMI_PMIC is not set +# CONFIG_PL330_DMA is not set +CONFIG_PM=y +CONFIG_PM_CLK=y +# CONFIG_PM_DEBUG is not set +CONFIG_PM_OPP=y +CONFIG_PM_SLEEP=y +CONFIG_PM_SLEEP_SMP=y +CONFIG_POWER_RESET=y +# CONFIG_POWER_RESET_BRCMSTB is not set +CONFIG_POWER_RESET_MSM=y +CONFIG_POWER_SUPPLY=y +CONFIG_PPS=y +CONFIG_PREEMPT=y +CONFIG_PREEMPT_COUNT=y +# CONFIG_PREEMPT_NONE is not set +CONFIG_PREEMPT_RCU=y +CONFIG_PRINTK_TIME=y +CONFIG_PROC_PAGE_MONITOR=y +CONFIG_PTP_1588_CLOCK=y +CONFIG_QCOM_ADM=y +CONFIG_QCOM_BAM_DMA=y +CONFIG_QCOM_GSBI=y +CONFIG_QCOM_HFPLL=y +CONFIG_QCOM_SCM=y +CONFIG_QCOM_SMEM=y +CONFIG_QCOM_WDT=y +CONFIG_RAS=y +# CONFIG_RCU_BOOST is not set +CONFIG_RCU_CPU_STALL_TIMEOUT=21 +CONFIG_RCU_STALL_COMMON=y +CONFIG_RD_GZIP=y +CONFIG_REGMAP=y +CONFIG_REGMAP_MMIO=y +CONFIG_REGULATOR=y +CONFIG_REGULATOR_QCOM_RPM=y +CONFIG_RESET_CONTROLLER=y +CONFIG_RFS_ACCEL=y +CONFIG_RPS=y +CONFIG_RTC_CLASS=y +# CONFIG_RTC_DRV_CMOS is not set +CONFIG_RWSEM_SPIN_ON_OWNER=y +CONFIG_RWSEM_XCHGADD_ALGORITHM=y +CONFIG_SCHED_HRTICK=y +# CONFIG_SCSI_DMA is not set +# CONFIG_SERIAL_AMBA_PL010 is not set +# CONFIG_SERIAL_AMBA_PL011 is not set +CONFIG_SERIAL_MSM=y +CONFIG_SERIAL_MSM_CONSOLE=y +# CONFIG_SLAB is not set +CONFIG_SLUB=y +CONFIG_SLUB_CPU_PARTIAL=y +CONFIG_SMP=y +CONFIG_SMP_ON_UP=y +CONFIG_SPARSE_IRQ=y +CONFIG_SPI=y +CONFIG_SPI_MASTER=y +CONFIG_SPI_QUP=y +CONFIG_SPMI=y +CONFIG_SPMI_MSM_PMIC_ARB=y +CONFIG_SRCU=y +CONFIG_STMMAC_ETH=y +CONFIG_STMMAC_PLATFORM=y +CONFIG_STOP_MACHINE=y +# CONFIG_STRIP_ASM_SYMS is not set +CONFIG_SUSPEND=y +CONFIG_SUSPEND_FREEZER=y +CONFIG_SWCONFIG=y +CONFIG_SWIOTLB=y +CONFIG_SWP_EMULATE=y +CONFIG_SYS_SUPPORTS_APM_EMULATION=y +CONFIG_THERMAL=y +# CONFIG_THERMAL_DEFAULT_GOV_FAIR_SHARE is not set +CONFIG_THERMAL_DEFAULT_GOV_STEP_WISE=y +# CONFIG_THERMAL_DEFAULT_GOV_USER_SPACE is not set +# CONFIG_THERMAL_EMULATION is not set +# CONFIG_THERMAL_GOV_FAIR_SHARE is not set +CONFIG_THERMAL_GOV_STEP_WISE=y +# CONFIG_THERMAL_GOV_USER_SPACE is not set +CONFIG_THERMAL_HWMON=y +CONFIG_THERMAL_OF=y +# CONFIG_THUMB2_KERNEL is not set +CONFIG_TICK_CPU_ACCOUNTING=y +CONFIG_TIMER_STATS=y +CONFIG_UBIFS_FS=y +# CONFIG_UBIFS_FS_ADVANCED_COMPR is not set +CONFIG_UBIFS_FS_LZO=y +CONFIG_UBIFS_FS_XZ=y +CONFIG_UBIFS_FS_ZLIB=y +CONFIG_UEVENT_HELPER_PATH="" +CONFIG_UID16=y +CONFIG_UNCOMPRESS_INCLUDE="debug/uncompress.h" +CONFIG_UNINLINE_SPIN_UNLOCK=y +CONFIG_USB_SUPPORT=y +CONFIG_USE_OF=y +CONFIG_VDSO=y +CONFIG_VECTORS_BASE=0xffff0000 +CONFIG_VFP=y +CONFIG_VFPv3=y +CONFIG_VM_EVENT_COUNTERS=y +CONFIG_WATCHDOG_CORE=y +# CONFIG_WIZNET_W5100 is not set +# CONFIG_WIZNET_W5300 is not set +# CONFIG_WL_TI is not set +# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set +CONFIG_XPS=y +CONFIG_XZ_DEC_ARM=y +CONFIG_XZ_DEC_BCJ=y +CONFIG_ZBOOT_ROM_BSS=0 +CONFIG_ZBOOT_ROM_TEXT=0 +CONFIG_ZLIB_DEFLATE=y +CONFIG_ZLIB_INFLATE=y +CONFIG_ZONE_DMA_FLAG=0 diff --git a/target/linux/ipq806x/image/Makefile b/target/linux/ipq806x/image/Makefile new file mode 100644 index 0000000..c59e6d4 --- /dev/null +++ b/target/linux/ipq806x/image/Makefile @@ -0,0 +1,124 @@ +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/image.mk + +UBIFS_OPTS = -m 2048 -e 124KiB -c 4096 -U -F +UBINIZE_OPTS = -m 2048 -p 128KiB + +KERNEL_LOADADDR := 0x42208000 + +define Image/Prepare + $(CP) $(LINUX_DIR)/vmlinux $(KDIR)/$(IMG_PREFIX)-vmlinux.elf + mkimage -A arm -O linux -T filesystem -C none \ + -a $(KERNEL_LOADADDR) -e $(KERNEL_LOADADDR) \ + -n 'ARM OpenWrt fakeroot' \ + -s $(KDIR_TMP)/root.dummy-uImage.tmp + echo -ne '\xff' > $(KDIR_TMP)/root.dummy + cat $(KDIR_TMP)/root.dummy $(KDIR_TMP)/root.dummy-uImage.tmp > $(KDIR)/root.dummy +endef + +define Image/BuildKernel + $(CP) $(KDIR)/$(IMG_PREFIX)-vmlinux.elf $(BIN_DIR) +endef + +define Image/Build/squashfs + cp $(KDIR)/root.squashfs $(KDIR)/root.squashfs-raw + $(call prepare_generic_squashfs,$(KDIR)/root.squashfs) +endef + +define Image/Build + $(call Image/Build/$(1),$(1)) + dd if=$(KDIR)/root.$(1) of=$(BIN_DIR)/$(IMG_PREFIX)-$(1)-root.img bs=2k conv=sync +endef + +define Build/append-dtb + cat $(DTS_DIR)/$(DEVICE_DTS).dtb >> $@ +endef + +define Build/append-file + cat $(1) >> $@ +endef + +define Device/Default + PROFILES := Default + KERNEL_INITRAMFS_PREFIX := $$(IMG_PREFIX)-$(1)-initramfs + DEVICE_DTS := + KERNEL_PREFIX := $$(IMAGE_PREFIX) + IMAGES := + KERNEL_IN_UBI := +endef +DEVICE_VARS += DEVICE_DTS KERNEL_IN_UBI + +define Device/LegacyImage + KERNEL_SUFFIX := -uImage + KERNEL = kernel-bin | append-dtb | uImage none + KERNEL_NAME := zImage +endef + +define Device/FitImage + KERNEL_SUFFIX := -fit-uImage.itb + KERNEL = kernel-bin | gzip | fit gzip $$(DTS_DIR)/$$(DEVICE_DTS).dtb + KERNEL_NAME := Image +endef + +define Device/UbiFit + KERNEL_IN_UBI := 1 + IMAGES := nand-factory.ubi nand-sysupgrade.tar + IMAGE/nand-factory.ubi := append-ubi + IMAGE/nand-sysupgrade.tar := sysupgrade-nand +endef + +define Device/DniImage + PROFILES += $$(DEVICE_NAME) + FILESYSTEMS := squashfs + KERNEL_SUFFIX := -uImage + KERNEL_INITRAMFS := kernel-bin | append-dtb | uImage none + KERNEL = kernel-bin | append-dtb | pad-to $$$$(($$(KERNEL_SIZE)-2*64-1)) | uImage none | append-file $(KDIR)/root.dummy + KERNEL_NAME := zImage + NETGEAR_BOARD_ID := + NETGEAR_HW_ID := + BOARD_NAME := + IMAGES := factory.img sysupgrade.tar + IMAGE/factory.img := append-kernel | append-ubi | netgear-dni + IMAGE/sysupgrade.tar = sysupgrade-nand +endef +DEVICE_VARS += KERNEL_SIZE NETGEAR_BOARD_ID NETGEAR_HW_ID DEVICE_BLOCK_SIZE DEVICE_PAGE_SIZE BOARD_NAME + +define Device/AP148 + $(call Device/FitImage) + $(call Device/UbiFit) + DEVICE_DTS := qcom-ipq8064-ap148 + BLOCKSIZE := 128KiB + PAGESIZE := 2048 +endef + +define Device/AP148-legacy + $(call Device/LegacyImage) + $(call Device/UbiFit) + DEVICE_DTS := qcom-ipq8064-ap148 + BLOCKSIZE := 128KiB + PAGESIZE := 2048 + BOARD_NAME := AP148 +endef + +define Device/DB149 + $(call Device/FitImage) + DEVICE_DTS := qcom-ipq8064-db149 + KERNEL_INSTALL := 1 +endef + +define Device/R7500 + $(call Device/DniImage) + DEVICE_DTS := qcom-ipq8064-r7500 + KERNEL_SIZE := 2097152 + NETGEAR_BOARD_ID := R7500 + NETGEAR_HW_ID := 29764841+0+128+256+3x3+4x4 + BLOCKSIZE := 128KiB + PAGESIZE := 2048 + BOARD_NAME := r7500 +endef + +TARGET_DEVICES += AP148 AP148-legacy DB149 R7500 + +$(eval $(call BuildImage)) diff --git a/target/linux/ipq806x/modules.mk b/target/linux/ipq806x/modules.mk new file mode 100644 index 0000000..04a2d4f --- /dev/null +++ b/target/linux/ipq806x/modules.mk @@ -0,0 +1,32 @@ +define KernelPackage/usb-phy-qcom-dwc3 + TITLE:=DWC3 USB QCOM PHY driver + DEPENDS:=@TARGET_ipq806x + KCONFIG:= CONFIG_PHY_QCOM_DWC3 + FILES:= $(LINUX_DIR)/drivers/phy/phy-qcom-dwc3.ko + AUTOLOAD:=$(call AutoLoad,45,phy-qcom-dwc3,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-phy-qcom-dwc3/description + This driver provides support for the integrated DesignWare + USB3 IP Core within the QCOM SoCs. +endef + +$(eval $(call KernelPackage,usb-phy-qcom-dwc3)) + + +define KernelPackage/usb-dwc3-qcom + TITLE:=DWC3 USB QCOM controller driver + DEPENDS:=@TARGET_ipq806x +kmod-usb-dwc3 +kmod-usb-phy-qcom-dwc3 + KCONFIG:= CONFIG_USB_DWC3_QCOM + FILES:= $(LINUX_DIR)/drivers/usb/dwc3/dwc3-qcom.ko + AUTOLOAD:=$(call AutoLoad,53,dwc3-qcom,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-dwc3-qcom/description + This driver provides support for the integrated DesignWare + USB3 IP Core within the QCOM SoCs. +endef + +$(eval $(call KernelPackage,usb-dwc3-qcom)) diff --git a/target/linux/ipq806x/patches-3.18/001-spi-qup-Add-DMA-capabilities.patch b/target/linux/ipq806x/patches-3.18/001-spi-qup-Add-DMA-capabilities.patch new file mode 100644 index 0000000..e110bf7 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/001-spi-qup-Add-DMA-capabilities.patch @@ -0,0 +1,522 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: spi: qup: Add DMA capabilities +From: Andy Gross +X-Patchwork-Id: 4432401 +Message-Id: <1403816781-31008-1-git-send-email-agross@codeaurora.org> +To: Mark Brown +Cc: linux-spi@vger.kernel.org, Sagar Dharia , + Daniel Sneddon , + Bjorn Andersson , + "Ivan T. Ivanov" , + linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + linux-arm-msm@vger.kernel.org, Andy Gross +Date: Thu, 26 Jun 2014 16:06:21 -0500 + +This patch adds DMA capabilities to the spi-qup driver. If DMA channels are +present, the QUP will use DMA instead of block mode for transfers to/from SPI +peripherals for transactions larger than the length of a block. + +Signed-off-by: Andy Gross + +--- +.../devicetree/bindings/spi/qcom,spi-qup.txt | 10 + + drivers/spi/spi-qup.c | 361 ++++++++++++++++++-- + 2 files changed, 350 insertions(+), 21 deletions(-) + +--- a/Documentation/devicetree/bindings/spi/qcom,spi-qup.txt ++++ b/Documentation/devicetree/bindings/spi/qcom,spi-qup.txt +@@ -27,6 +27,11 @@ Optional properties: + - spi-max-frequency: Specifies maximum SPI clock frequency, + Units - Hz. Definition as per + Documentation/devicetree/bindings/spi/spi-bus.txt ++- dmas : Two DMA channel specifiers following the convention outlined ++ in bindings/dma/dma.txt ++- dma-names: Names for the dma channels, if present. There must be at ++ least one channel named "tx" for transmit and named "rx" for ++ receive. + - num-cs: total number of chipselects + - cs-gpios: should specify GPIOs used for chipselects. + The gpios will be referred to as reg = in the SPI child +@@ -51,6 +56,10 @@ Example: + clocks = <&gcc GCC_BLSP2_QUP2_SPI_APPS_CLK>, <&gcc GCC_BLSP2_AHB_CLK>; + clock-names = "core", "iface"; + ++ dmas = <&blsp2_bam 2>, ++ <&blsp2_bam 3>; ++ dma-names = "rx", "tx"; ++ + pinctrl-names = "default"; + pinctrl-0 = <&spi8_default>; + +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -22,6 +22,8 @@ + #include + #include + #include ++#include ++#include + + #define QUP_CONFIG 0x0000 + #define QUP_STATE 0x0004 +@@ -116,6 +118,8 @@ + + #define SPI_NUM_CHIPSELECTS 4 + ++#define SPI_MAX_XFER (SZ_64K - 64) ++ + /* high speed mode is when bus rate is greater then 26MHz */ + #define SPI_HS_MIN_RATE 26000000 + #define SPI_MAX_RATE 50000000 +@@ -143,6 +147,17 @@ struct spi_qup { + int tx_bytes; + int rx_bytes; + int qup_v1; ++ ++ int use_dma; ++ ++ struct dma_chan *rx_chan; ++ struct dma_slave_config rx_conf; ++ struct dma_chan *tx_chan; ++ struct dma_slave_config tx_conf; ++ dma_addr_t rx_dma; ++ dma_addr_t tx_dma; ++ void *dummy; ++ atomic_t dma_outstanding; + }; + + +@@ -266,6 +281,221 @@ static void spi_qup_fifo_write(struct sp + } + } + ++static void qup_dma_callback(void *data) ++{ ++ struct spi_qup *controller = data; ++ ++ if (atomic_dec_and_test(&controller->dma_outstanding)) ++ complete(&controller->done); ++} ++ ++static int spi_qup_do_dma(struct spi_qup *controller, struct spi_transfer *xfer) ++{ ++ struct dma_async_tx_descriptor *rxd, *txd; ++ dma_cookie_t rx_cookie, tx_cookie; ++ u32 xfer_len, rx_align = 0, tx_align = 0, n_words; ++ struct scatterlist tx_sg[2], rx_sg[2]; ++ int ret = 0; ++ u32 bytes_to_xfer = xfer->len; ++ u32 offset = 0; ++ u32 rx_nents = 0, tx_nents = 0; ++ dma_addr_t rx_dma = 0, tx_dma = 0, rx_dummy_dma = 0, tx_dummy_dma = 0; ++ ++ ++ if (xfer->rx_buf) { ++ rx_dma = dma_map_single(controller->dev, xfer->rx_buf, ++ xfer->len, DMA_FROM_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, rx_dma)) { ++ ret = -ENOMEM; ++ return ret; ++ } ++ ++ /* check to see if we need dummy buffer for leftover bytes */ ++ rx_align = xfer->len % controller->in_blk_sz; ++ if (rx_align) { ++ rx_dummy_dma = dma_map_single(controller->dev, ++ controller->dummy, controller->in_fifo_sz, ++ DMA_FROM_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, rx_dummy_dma)) { ++ ret = -ENOMEM; ++ goto err_map_rx_dummy; ++ } ++ } ++ } ++ ++ if (xfer->tx_buf) { ++ tx_dma = dma_map_single(controller->dev, ++ (void *)xfer->tx_buf, xfer->len, DMA_TO_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, tx_dma)) { ++ ret = -ENOMEM; ++ goto err_map_tx; ++ } ++ ++ /* check to see if we need dummy buffer for leftover bytes */ ++ tx_align = xfer->len % controller->out_blk_sz; ++ if (tx_align) { ++ memcpy(controller->dummy + SZ_1K, ++ xfer->tx_buf + xfer->len - tx_align, ++ tx_align); ++ memset(controller->dummy + SZ_1K + tx_align, 0, ++ controller->out_blk_sz - tx_align); ++ ++ tx_dummy_dma = dma_map_single(controller->dev, ++ controller->dummy + SZ_1K, ++ controller->out_blk_sz, DMA_TO_DEVICE); ++ ++ if (dma_mapping_error(controller->dev, tx_dummy_dma)) { ++ ret = -ENOMEM; ++ goto err_map_tx_dummy; ++ } ++ } ++ } ++ ++ atomic_set(&controller->dma_outstanding, 0); ++ ++ while (bytes_to_xfer > 0) { ++ xfer_len = min_t(u32, bytes_to_xfer, SPI_MAX_XFER); ++ n_words = DIV_ROUND_UP(xfer_len, controller->w_size); ++ ++ /* write out current word count to controller */ ++ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); ++ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); ++ ++ reinit_completion(&controller->done); ++ ++ if (xfer->tx_buf) { ++ /* recalc align for each transaction */ ++ tx_align = xfer_len % controller->out_blk_sz; ++ ++ if (tx_align) ++ tx_nents = 2; ++ else ++ tx_nents = 1; ++ ++ /* initialize scatterlists */ ++ sg_init_table(tx_sg, tx_nents); ++ sg_dma_len(&tx_sg[0]) = xfer_len - tx_align; ++ sg_dma_address(&tx_sg[0]) = tx_dma + offset; ++ ++ /* account for non block size transfer */ ++ if (tx_align) { ++ sg_dma_len(&tx_sg[1]) = controller->out_blk_sz; ++ sg_dma_address(&tx_sg[1]) = tx_dummy_dma; ++ } ++ ++ txd = dmaengine_prep_slave_sg(controller->tx_chan, ++ tx_sg, tx_nents, DMA_MEM_TO_DEV, 0); ++ if (!txd) { ++ ret = -ENOMEM; ++ goto err_unmap; ++ } ++ ++ atomic_inc(&controller->dma_outstanding); ++ ++ txd->callback = qup_dma_callback; ++ txd->callback_param = controller; ++ ++ tx_cookie = dmaengine_submit(txd); ++ ++ dma_async_issue_pending(controller->tx_chan); ++ } ++ ++ if (xfer->rx_buf) { ++ /* recalc align for each transaction */ ++ rx_align = xfer_len % controller->in_blk_sz; ++ ++ if (rx_align) ++ rx_nents = 2; ++ else ++ rx_nents = 1; ++ ++ /* initialize scatterlists */ ++ sg_init_table(rx_sg, rx_nents); ++ sg_dma_address(&rx_sg[0]) = rx_dma + offset; ++ sg_dma_len(&rx_sg[0]) = xfer_len - rx_align; ++ ++ /* account for non block size transfer */ ++ if (rx_align) { ++ sg_dma_len(&rx_sg[1]) = controller->in_blk_sz; ++ sg_dma_address(&rx_sg[1]) = rx_dummy_dma; ++ } ++ ++ rxd = dmaengine_prep_slave_sg(controller->rx_chan, ++ rx_sg, rx_nents, DMA_DEV_TO_MEM, 0); ++ if (!rxd) { ++ ret = -ENOMEM; ++ goto err_unmap; ++ } ++ ++ atomic_inc(&controller->dma_outstanding); ++ ++ rxd->callback = qup_dma_callback; ++ rxd->callback_param = controller; ++ ++ rx_cookie = dmaengine_submit(rxd); ++ ++ dma_async_issue_pending(controller->rx_chan); ++ } ++ ++ if (spi_qup_set_state(controller, QUP_STATE_RUN)) { ++ dev_warn(controller->dev, "cannot set EXECUTE state\n"); ++ goto err_unmap; ++ } ++ ++ if (!wait_for_completion_timeout(&controller->done, ++ msecs_to_jiffies(1000))) { ++ ret = -ETIMEDOUT; ++ ++ /* clear out all the DMA transactions */ ++ if (xfer->tx_buf) ++ dmaengine_terminate_all(controller->tx_chan); ++ if (xfer->rx_buf) ++ dmaengine_terminate_all(controller->rx_chan); ++ ++ goto err_unmap; ++ } ++ ++ if (rx_align) ++ memcpy(xfer->rx_buf + offset + xfer->len - rx_align, ++ controller->dummy, rx_align); ++ ++ /* adjust remaining bytes to transfer */ ++ bytes_to_xfer -= xfer_len; ++ offset += xfer_len; ++ ++ ++ /* reset mini-core state so we can program next transaction */ ++ if (spi_qup_set_state(controller, QUP_STATE_RESET)) { ++ dev_err(controller->dev, "cannot set RESET state\n"); ++ goto err_unmap; ++ } ++ } ++ ++ ret = 0; ++ ++err_unmap: ++ if (tx_align) ++ dma_unmap_single(controller->dev, tx_dummy_dma, ++ controller->out_fifo_sz, DMA_TO_DEVICE); ++err_map_tx_dummy: ++ if (xfer->tx_buf) ++ dma_unmap_single(controller->dev, tx_dma, xfer->len, ++ DMA_TO_DEVICE); ++err_map_tx: ++ if (rx_align) ++ dma_unmap_single(controller->dev, rx_dummy_dma, ++ controller->in_fifo_sz, DMA_FROM_DEVICE); ++err_map_rx_dummy: ++ if (xfer->rx_buf) ++ dma_unmap_single(controller->dev, rx_dma, xfer->len, ++ DMA_FROM_DEVICE); ++ ++ return ret; ++} ++ + static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + { + struct spi_qup *controller = dev_id; +@@ -315,11 +545,13 @@ static irqreturn_t spi_qup_qup_irq(int i + error = -EIO; + } + +- if (opflags & QUP_OP_IN_SERVICE_FLAG) +- spi_qup_fifo_read(controller, xfer); ++ if (!controller->use_dma) { ++ if (opflags & QUP_OP_IN_SERVICE_FLAG) ++ spi_qup_fifo_read(controller, xfer); + +- if (opflags & QUP_OP_OUT_SERVICE_FLAG) +- spi_qup_fifo_write(controller, xfer); ++ if (opflags & QUP_OP_OUT_SERVICE_FLAG) ++ spi_qup_fifo_write(controller, xfer); ++ } + + spin_lock_irqsave(&controller->lock, flags); + controller->error = error; +@@ -339,6 +571,8 @@ static int spi_qup_io_config(struct spi_ + struct spi_qup *controller = spi_master_get_devdata(spi->master); + u32 config, iomode, mode; + int ret, n_words, w_size; ++ size_t dma_align = dma_get_cache_alignment(); ++ u32 dma_available = 0; + + if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) { + dev_err(controller->dev, "too big size for loopback %d > %d\n", +@@ -367,6 +601,11 @@ static int spi_qup_io_config(struct spi_ + n_words = xfer->len / w_size; + controller->w_size = w_size; + ++ if (controller->rx_chan && ++ IS_ALIGNED((size_t)xfer->tx_buf, dma_align) && ++ IS_ALIGNED((size_t)xfer->rx_buf, dma_align)) ++ dma_available = 1; ++ + if (n_words <= (controller->in_fifo_sz / sizeof(u32))) { + mode = QUP_IO_M_MODE_FIFO; + writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT); +@@ -374,19 +613,31 @@ static int spi_qup_io_config(struct spi_ + /* must be zero for FIFO */ + writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); +- } else { ++ controller->use_dma = 0; ++ } else if (!dma_available) { + mode = QUP_IO_M_MODE_BLOCK; + writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); + /* must be zero for BLOCK and BAM */ + writel_relaxed(0, controller->base + QUP_MX_READ_CNT); + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); ++ controller->use_dma = 0; ++ } else { ++ mode = QUP_IO_M_MODE_DMOV; ++ writel_relaxed(0, controller->base + QUP_MX_READ_CNT); ++ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); ++ controller->use_dma = 1; + } + + iomode = readl_relaxed(controller->base + QUP_IO_M_MODES); + /* Set input and output transfer mode */ + iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK); +- iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN); ++ ++ if (!controller->use_dma) ++ iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN); ++ else ++ iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN; ++ + iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); + iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); + +@@ -419,6 +670,14 @@ static int spi_qup_io_config(struct spi_ + config &= ~(QUP_CONFIG_NO_INPUT | QUP_CONFIG_NO_OUTPUT | QUP_CONFIG_N); + config |= xfer->bits_per_word - 1; + config |= QUP_CONFIG_SPI_MODE; ++ ++ if (controller->use_dma) { ++ if (!xfer->tx_buf) ++ config |= QUP_CONFIG_NO_OUTPUT; ++ if (!xfer->rx_buf) ++ config |= QUP_CONFIG_NO_INPUT; ++ } ++ + writel_relaxed(config, controller->base + QUP_CONFIG); + + /* only write to OPERATIONAL_MASK when register is present */ +@@ -452,25 +711,29 @@ static int spi_qup_transfer_one(struct s + controller->tx_bytes = 0; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (spi_qup_set_state(controller, QUP_STATE_RUN)) { +- dev_warn(controller->dev, "cannot set RUN state\n"); +- goto exit; +- } ++ if (controller->use_dma) { ++ ret = spi_qup_do_dma(controller, xfer); ++ } else { ++ if (spi_qup_set_state(controller, QUP_STATE_RUN)) { ++ dev_warn(controller->dev, "cannot set RUN state\n"); ++ goto exit; ++ } + +- if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) { +- dev_warn(controller->dev, "cannot set PAUSE state\n"); +- goto exit; +- } ++ if (spi_qup_set_state(controller, QUP_STATE_PAUSE)) { ++ dev_warn(controller->dev, "cannot set PAUSE state\n"); ++ goto exit; ++ } + +- spi_qup_fifo_write(controller, xfer); ++ spi_qup_fifo_write(controller, xfer); + +- if (spi_qup_set_state(controller, QUP_STATE_RUN)) { +- dev_warn(controller->dev, "cannot set EXECUTE state\n"); +- goto exit; +- } ++ if (spi_qup_set_state(controller, QUP_STATE_RUN)) { ++ dev_warn(controller->dev, "cannot set EXECUTE state\n"); ++ goto exit; ++ } + +- if (!wait_for_completion_timeout(&controller->done, timeout)) +- ret = -ETIMEDOUT; ++ if (!wait_for_completion_timeout(&controller->done, timeout)) ++ ret = -ETIMEDOUT; ++ } + exit: + spi_qup_set_state(controller, QUP_STATE_RESET); + spin_lock_irqsave(&controller->lock, flags); +@@ -554,6 +817,7 @@ static int spi_qup_probe(struct platform + master->transfer_one = spi_qup_transfer_one; + master->dev.of_node = pdev->dev.of_node; + master->auto_runtime_pm = true; ++ master->dma_alignment = dma_get_cache_alignment(); + + platform_set_drvdata(pdev, master); + +@@ -619,6 +883,56 @@ static int spi_qup_probe(struct platform + QUP_ERROR_INPUT_UNDER_RUN | QUP_ERROR_OUTPUT_UNDER_RUN, + base + QUP_ERROR_FLAGS_EN); + ++ /* allocate dma resources, if available */ ++ controller->rx_chan = dma_request_slave_channel(&pdev->dev, "rx"); ++ if (controller->rx_chan) { ++ controller->tx_chan = ++ dma_request_slave_channel(&pdev->dev, "tx"); ++ ++ if (!controller->tx_chan) { ++ dev_err(&pdev->dev, "Failed to allocate dma tx chan"); ++ dma_release_channel(controller->rx_chan); ++ } ++ ++ /* set DMA parameters */ ++ controller->rx_conf.device_fc = 1; ++ controller->rx_conf.src_addr = res->start + QUP_INPUT_FIFO; ++ controller->rx_conf.src_maxburst = controller->in_blk_sz; ++ ++ controller->tx_conf.device_fc = 1; ++ controller->tx_conf.dst_addr = res->start + QUP_OUTPUT_FIFO; ++ controller->tx_conf.dst_maxburst = controller->out_blk_sz; ++ ++ if (dmaengine_slave_config(controller->rx_chan, ++ &controller->rx_conf)) { ++ dev_err(&pdev->dev, "failed to configure RX channel\n"); ++ ++ dma_release_channel(controller->rx_chan); ++ dma_release_channel(controller->tx_chan); ++ controller->tx_chan = NULL; ++ controller->rx_chan = NULL; ++ } else if (dmaengine_slave_config(controller->tx_chan, ++ &controller->tx_conf)) { ++ dev_err(&pdev->dev, "failed to configure TX channel\n"); ++ ++ dma_release_channel(controller->rx_chan); ++ dma_release_channel(controller->tx_chan); ++ controller->tx_chan = NULL; ++ controller->rx_chan = NULL; ++ } ++ ++ controller->dummy = devm_kmalloc(controller->dev, PAGE_SIZE, ++ GFP_KERNEL); ++ ++ if (!controller->dummy) { ++ dma_release_channel(controller->rx_chan); ++ dma_release_channel(controller->tx_chan); ++ controller->tx_chan = NULL; ++ controller->rx_chan = NULL; ++ } ++ } ++ ++ + writel_relaxed(0, base + SPI_CONFIG); + writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL); + +@@ -731,6 +1045,11 @@ static int spi_qup_remove(struct platfor + if (ret) + return ret; + ++ if (controller->rx_chan) ++ dma_release_channel(controller->rx_chan); ++ if (controller->tx_chan) ++ dma_release_channel(controller->tx_chan); ++ + clk_disable_unprepare(controller->cclk); + clk_disable_unprepare(controller->iclk); + diff --git a/target/linux/ipq806x/patches-3.18/002-v3-spi-qup-Fix-incorrect-block-transfers.patch b/target/linux/ipq806x/patches-3.18/002-v3-spi-qup-Fix-incorrect-block-transfers.patch new file mode 100644 index 0000000..62ee5b4 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/002-v3-spi-qup-Fix-incorrect-block-transfers.patch @@ -0,0 +1,376 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3] spi: qup: Fix incorrect block transfers +From: Andy Gross +X-Patchwork-Id: 5007321 +Message-Id: <1412112088-25928-1-git-send-email-agross@codeaurora.org> +To: Mark Brown +Cc: linux-spi@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, linux-arm-msm@vger.kernel.org, + "Ivan T. Ivanov" , + Bjorn Andersson , + Kumar Gala , Andy Gross +Date: Tue, 30 Sep 2014 16:21:28 -0500 + +This patch fixes a number of errors with the QUP block transfer mode. Errors +manifested themselves as input underruns, output overruns, and timed out +transactions. + +The block mode does not require the priming that occurs in FIFO mode. At the +moment that the QUP is placed into the RUN state, the QUP will immediately raise +an interrupt if the request is a write. Therefore, there is no need to prime +the pump. + +In addition, the block transfers require that whole blocks of data are +read/written at a time. The last block of data that completes a transaction may +contain less than a full blocks worth of data. + +Each block of data results in an input/output service interrupt accompanied with +a input/output block flag set. Additional block reads/writes require clearing +of the service flag. It is ok to check for additional blocks of data in the +ISR, but you have to ack every block you transfer. Imbalanced acks result in +early return from complete transactions with pending interrupts that still have +to be ack'd. The next transaction can be affected by these interrupts. +Transactions are deemed complete when the MAX_INPUT or MAX_OUTPUT flag are set. + +Changes from v2: +- Added in additional completion check so that transaction done is not + prematurely signaled. +- Fixed various review comments. + +Changes from v1: +- Split out read/write block function. +- Removed extraneous checks for transfer length + +Signed-off-by: Andy Gross + +--- +drivers/spi/spi-qup.c | 201 ++++++++++++++++++++++++++++++++++++------------- + 1 file changed, 148 insertions(+), 53 deletions(-) + +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -82,6 +82,8 @@ + #define QUP_IO_M_MODE_BAM 3 + + /* QUP_OPERATIONAL fields */ ++#define QUP_OP_IN_BLOCK_READ_REQ BIT(13) ++#define QUP_OP_OUT_BLOCK_WRITE_REQ BIT(12) + #define QUP_OP_MAX_INPUT_DONE_FLAG BIT(11) + #define QUP_OP_MAX_OUTPUT_DONE_FLAG BIT(10) + #define QUP_OP_IN_SERVICE_FLAG BIT(9) +@@ -147,6 +149,7 @@ struct spi_qup { + int tx_bytes; + int rx_bytes; + int qup_v1; ++ int mode; + + int use_dma; + +@@ -213,30 +216,14 @@ static int spi_qup_set_state(struct spi_ + return 0; + } + +- +-static void spi_qup_fifo_read(struct spi_qup *controller, +- struct spi_transfer *xfer) ++static void spi_qup_fill_read_buffer(struct spi_qup *controller, ++ struct spi_transfer *xfer, u32 data) + { + u8 *rx_buf = xfer->rx_buf; +- u32 word, state; +- int idx, shift, w_size; +- +- w_size = controller->w_size; +- +- while (controller->rx_bytes < xfer->len) { +- +- state = readl_relaxed(controller->base + QUP_OPERATIONAL); +- if (0 == (state & QUP_OP_IN_FIFO_NOT_EMPTY)) +- break; +- +- word = readl_relaxed(controller->base + QUP_INPUT_FIFO); +- +- if (!rx_buf) { +- controller->rx_bytes += w_size; +- continue; +- } ++ int idx, shift; + +- for (idx = 0; idx < w_size; idx++, controller->rx_bytes++) { ++ if (rx_buf) ++ for (idx = 0; idx < controller->w_size; idx++) { + /* + * The data format depends on bytes per SPI word: + * 4 bytes: 0x12345678 +@@ -244,41 +231,139 @@ static void spi_qup_fifo_read(struct spi + * 1 byte : 0x00000012 + */ + shift = BITS_PER_BYTE; +- shift *= (w_size - idx - 1); +- rx_buf[controller->rx_bytes] = word >> shift; ++ shift *= (controller->w_size - idx - 1); ++ rx_buf[controller->rx_bytes + idx] = data >> shift; ++ } ++ ++ controller->rx_bytes += controller->w_size; ++} ++ ++static void spi_qup_prepare_write_data(struct spi_qup *controller, ++ struct spi_transfer *xfer, u32 *data) ++{ ++ const u8 *tx_buf = xfer->tx_buf; ++ u32 val; ++ int idx; ++ ++ *data = 0; ++ ++ if (tx_buf) ++ for (idx = 0; idx < controller->w_size; idx++) { ++ val = tx_buf[controller->tx_bytes + idx]; ++ *data |= val << (BITS_PER_BYTE * (3 - idx)); + } ++ ++ controller->tx_bytes += controller->w_size; ++} ++ ++static void spi_qup_fifo_read(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ u32 data; ++ ++ /* clear service request */ ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ while (controller->rx_bytes < xfer->len) { ++ if (!(readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_IN_FIFO_NOT_EMPTY)) ++ break; ++ ++ data = readl_relaxed(controller->base + QUP_INPUT_FIFO); ++ ++ spi_qup_fill_read_buffer(controller, xfer, data); + } + } + + static void spi_qup_fifo_write(struct spi_qup *controller, +- struct spi_transfer *xfer) ++ struct spi_transfer *xfer) + { +- const u8 *tx_buf = xfer->tx_buf; +- u32 word, state, data; +- int idx, w_size; ++ u32 data; + +- w_size = controller->w_size; ++ /* clear service request */ ++ writel_relaxed(QUP_OP_OUT_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); + + while (controller->tx_bytes < xfer->len) { + +- state = readl_relaxed(controller->base + QUP_OPERATIONAL); +- if (state & QUP_OP_OUT_FIFO_FULL) ++ if (readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_OUT_FIFO_FULL) + break; + +- word = 0; +- for (idx = 0; idx < w_size; idx++, controller->tx_bytes++) { ++ spi_qup_prepare_write_data(controller, xfer, &data); ++ writel_relaxed(data, controller->base + QUP_OUTPUT_FIFO); + +- if (!tx_buf) { +- controller->tx_bytes += w_size; +- break; +- } ++ } ++} + +- data = tx_buf[controller->tx_bytes]; +- word |= data << (BITS_PER_BYTE * (3 - idx)); +- } ++static void spi_qup_block_read(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ u32 data; ++ u32 reads_per_blk = controller->in_blk_sz >> 2; ++ u32 num_words = (xfer->len - controller->rx_bytes) / controller->w_size; ++ int i; ++ ++ do { ++ /* ACK by clearing service flag */ ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ /* transfer up to a block size of data in a single pass */ ++ for (i = 0; num_words && i < reads_per_blk; i++, num_words--) { ++ ++ /* read data and fill up rx buffer */ ++ data = readl_relaxed(controller->base + QUP_INPUT_FIFO); ++ spi_qup_fill_read_buffer(controller, xfer, data); ++ } ++ ++ /* check to see if next block is ready */ ++ if (!(readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_IN_BLOCK_READ_REQ)) ++ break; + +- writel_relaxed(word, controller->base + QUP_OUTPUT_FIFO); +- } ++ } while (num_words); ++ ++ /* ++ * Due to extra stickiness of the QUP_OP_IN_SERVICE_FLAG during block ++ * reads, it has to be cleared again at the very end ++ */ ++ if (readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_MAX_INPUT_DONE_FLAG) ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++} ++ ++static void spi_qup_block_write(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ u32 data; ++ u32 writes_per_blk = controller->out_blk_sz >> 2; ++ u32 num_words = (xfer->len - controller->tx_bytes) / controller->w_size; ++ int i; ++ ++ do { ++ /* ACK by clearing service flag */ ++ writel_relaxed(QUP_OP_OUT_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ /* transfer up to a block size of data in a single pass */ ++ for (i = 0; num_words && i < writes_per_blk; i++, num_words--) { ++ ++ /* swizzle the bytes for output and write out */ ++ spi_qup_prepare_write_data(controller, xfer, &data); ++ writel_relaxed(data, ++ controller->base + QUP_OUTPUT_FIFO); ++ } ++ ++ /* check to see if next block is ready */ ++ if (!(readl_relaxed(controller->base + QUP_OPERATIONAL) & ++ QUP_OP_OUT_BLOCK_WRITE_REQ)) ++ break; ++ ++ } while (num_words); + } + + static void qup_dma_callback(void *data) +@@ -515,9 +600,9 @@ static irqreturn_t spi_qup_qup_irq(int i + + writel_relaxed(qup_err, controller->base + QUP_ERROR_FLAGS); + writel_relaxed(spi_err, controller->base + SPI_ERROR_FLAGS); +- writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); + + if (!xfer) { ++ writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); + dev_err_ratelimited(controller->dev, "unexpected irq %08x %08x %08x\n", + qup_err, spi_err, opflags); + return IRQ_HANDLED; +@@ -546,11 +631,19 @@ static irqreturn_t spi_qup_qup_irq(int i + } + + if (!controller->use_dma) { +- if (opflags & QUP_OP_IN_SERVICE_FLAG) +- spi_qup_fifo_read(controller, xfer); ++ if (opflags & QUP_OP_IN_SERVICE_FLAG) { ++ if (opflags & QUP_OP_IN_BLOCK_READ_REQ) ++ spi_qup_block_read(controller, xfer); ++ else ++ spi_qup_fifo_read(controller, xfer); ++ } + +- if (opflags & QUP_OP_OUT_SERVICE_FLAG) +- spi_qup_fifo_write(controller, xfer); ++ if (opflags & QUP_OP_OUT_SERVICE_FLAG) { ++ if (opflags & QUP_OP_OUT_BLOCK_WRITE_REQ) ++ spi_qup_block_write(controller, xfer); ++ else ++ spi_qup_fifo_write(controller, xfer); ++ } + } + + spin_lock_irqsave(&controller->lock, flags); +@@ -558,7 +651,8 @@ static irqreturn_t spi_qup_qup_irq(int i + controller->xfer = xfer; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (controller->rx_bytes == xfer->len || error) ++ if ((controller->rx_bytes == xfer->len && ++ (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error) + complete(&controller->done); + + return IRQ_HANDLED; +@@ -569,7 +663,7 @@ static irqreturn_t spi_qup_qup_irq(int i + static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + { + struct spi_qup *controller = spi_master_get_devdata(spi->master); +- u32 config, iomode, mode; ++ u32 config, iomode; + int ret, n_words, w_size; + size_t dma_align = dma_get_cache_alignment(); + u32 dma_available = 0; +@@ -607,7 +701,7 @@ static int spi_qup_io_config(struct spi_ + dma_available = 1; + + if (n_words <= (controller->in_fifo_sz / sizeof(u32))) { +- mode = QUP_IO_M_MODE_FIFO; ++ controller->mode = QUP_IO_M_MODE_FIFO; + writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT); + /* must be zero for FIFO */ +@@ -615,7 +709,7 @@ static int spi_qup_io_config(struct spi_ + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); + controller->use_dma = 0; + } else if (!dma_available) { +- mode = QUP_IO_M_MODE_BLOCK; ++ controller->mode = QUP_IO_M_MODE_BLOCK; + writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); + /* must be zero for BLOCK and BAM */ +@@ -623,7 +717,7 @@ static int spi_qup_io_config(struct spi_ + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + controller->use_dma = 0; + } else { +- mode = QUP_IO_M_MODE_DMOV; ++ controller->mode = QUP_IO_M_MODE_DMOV; + writel_relaxed(0, controller->base + QUP_MX_READ_CNT); + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + controller->use_dma = 1; +@@ -638,8 +732,8 @@ static int spi_qup_io_config(struct spi_ + else + iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN; + +- iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); +- iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); ++ iomode |= (controller->mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); ++ iomode |= (controller->mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); + + writel_relaxed(iomode, controller->base + QUP_IO_M_MODES); + +@@ -724,7 +818,8 @@ static int spi_qup_transfer_one(struct s + goto exit; + } + +- spi_qup_fifo_write(controller, xfer); ++ if (controller->mode == QUP_IO_M_MODE_FIFO) ++ spi_qup_fifo_write(controller, xfer); + + if (spi_qup_set_state(controller, QUP_STATE_RUN)) { + dev_warn(controller->dev, "cannot set EXECUTE state\n"); +@@ -741,6 +836,7 @@ exit: + if (!ret) + ret = controller->error; + spin_unlock_irqrestore(&controller->lock, flags); ++ + return ret; + } + diff --git a/target/linux/ipq806x/patches-3.18/003-spi-qup-Ensure-done-detection.patch b/target/linux/ipq806x/patches-3.18/003-spi-qup-Ensure-done-detection.patch new file mode 100644 index 0000000..7052227 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/003-spi-qup-Ensure-done-detection.patch @@ -0,0 +1,56 @@ +From 4faba89e3ffbb1c5f6232651375b9b3212b50f02 Mon Sep 17 00:00:00 2001 +From: Andy Gross +Date: Thu, 15 Jan 2015 17:56:02 -0800 +Subject: [PATCH] spi: qup: Ensure done detection + +This patch fixes an issue where a SPI transaction has completed, but the done +condition is missed. This occurs because at the time of interrupt the +MAX_INPUT_DONE_FLAG is not asserted. However, in the process of reading blocks +of data from the FIFO, the last portion of data comes in. + +The opflags read at the beginning of the irq handler no longer matches the +current opflag state. To get around this condition, the block read function +should update the opflags so that done detection is correct after the return. + +Change-Id: If109e0eeb432f96000d765c4b34dbb2269f8093f +Signed-off-by: Andy Gross +--- + drivers/spi/spi-qup.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -298,7 +298,7 @@ static void spi_qup_fifo_write(struct sp + } + + static void spi_qup_block_read(struct spi_qup *controller, +- struct spi_transfer *xfer) ++ struct spi_transfer *xfer, u32 *opflags) + { + u32 data; + u32 reads_per_blk = controller->in_blk_sz >> 2; +@@ -327,10 +327,12 @@ static void spi_qup_block_read(struct sp + + /* + * Due to extra stickiness of the QUP_OP_IN_SERVICE_FLAG during block +- * reads, it has to be cleared again at the very end ++ * reads, it has to be cleared again at the very end. However, be sure ++ * to refresh opflags value because MAX_INPUT_DONE_FLAG may now be ++ * present and this is used to determine if transaction is complete + */ +- if (readl_relaxed(controller->base + QUP_OPERATIONAL) & +- QUP_OP_MAX_INPUT_DONE_FLAG) ++ *opflags = readl_relaxed(controller->base + QUP_OPERATIONAL); ++ if (*opflags & QUP_OP_MAX_INPUT_DONE_FLAG) + writel_relaxed(QUP_OP_IN_SERVICE_FLAG, + controller->base + QUP_OPERATIONAL); + +@@ -633,7 +635,7 @@ static irqreturn_t spi_qup_qup_irq(int i + if (!controller->use_dma) { + if (opflags & QUP_OP_IN_SERVICE_FLAG) { + if (opflags & QUP_OP_IN_BLOCK_READ_REQ) +- spi_qup_block_read(controller, xfer); ++ spi_qup_block_read(controller, xfer, &opflags); + else + spi_qup_fifo_read(controller, xfer); + } diff --git a/target/linux/ipq806x/patches-3.18/011-watchdog-qcom-use-timer-devicetree-binding.patch b/target/linux/ipq806x/patches-3.18/011-watchdog-qcom-use-timer-devicetree-binding.patch new file mode 100644 index 0000000..68489a8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/011-watchdog-qcom-use-timer-devicetree-binding.patch @@ -0,0 +1,67 @@ +From fded70251b1b58f68de1d3757ece9965f0b75452 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 19 Feb 2015 20:19:30 -0800 +Subject: [PATCH 1/3] watchdog: qcom: use timer devicetree binding + +MSM watchdog configuration happens in the same register block as the +timer, so we'll use the same binding as the existing timer. + +The qcom-wdt will now be probed when devicetree has an entry compatible +with "qcom,kpss-timer" or "qcom-scss-timer". + +Signed-off-by: Mathieu Olivari +--- + drivers/watchdog/qcom-wdt.c | 21 +++++++++++++++------ + 1 file changed, 15 insertions(+), 6 deletions(-) + +--- a/drivers/watchdog/qcom-wdt.c ++++ b/drivers/watchdog/qcom-wdt.c +@@ -20,9 +20,9 @@ + #include + #include + +-#define WDT_RST 0x0 +-#define WDT_EN 0x8 +-#define WDT_BITE_TIME 0x24 ++#define WDT_RST 0x38 ++#define WDT_EN 0x40 ++#define WDT_BITE_TIME 0x5C + + struct qcom_wdt { + struct watchdog_device wdd; +@@ -117,6 +117,8 @@ static int qcom_wdt_probe(struct platfor + { + struct qcom_wdt *wdt; + struct resource *res; ++ struct device_node *np = pdev->dev.of_node; ++ u32 percpu_offset; + int ret; + + wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); +@@ -124,6 +126,14 @@ static int qcom_wdt_probe(struct platfor + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ ++ /* We use CPU0's DGT for the watchdog */ ++ if (of_property_read_u32(np, "cpu-offset", &percpu_offset)) ++ percpu_offset = 0; ++ ++ res->start += percpu_offset; ++ res->end += percpu_offset; ++ + wdt->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(wdt->base)) + return PTR_ERR(wdt->base); +@@ -203,9 +213,8 @@ static int qcom_wdt_remove(struct platfo + } + + static const struct of_device_id qcom_wdt_of_table[] = { +- { .compatible = "qcom,kpss-wdt-msm8960", }, +- { .compatible = "qcom,kpss-wdt-apq8064", }, +- { .compatible = "qcom,kpss-wdt-ipq8064", }, ++ { .compatible = "qcom,kpss-timer" }, ++ { .compatible = "qcom,scss-timer" }, + { }, + }; + MODULE_DEVICE_TABLE(of, qcom_wdt_of_table); diff --git a/target/linux/ipq806x/patches-3.18/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch b/target/linux/ipq806x/patches-3.18/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch new file mode 100644 index 0000000..ae96776 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/012-ARM-qcom-add-description-of-KPSS-WDT-for-IPQ8064.patch @@ -0,0 +1,48 @@ +From 297cf8136ecd6a56520888fd28948393766b8ee7 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 19 Feb 2015 20:27:39 -0800 +Subject: [PATCH 2/3] ARM: qcom: add description of KPSS WDT for IPQ8064 + +Add the watchdog related entries to the Krait Processor Sub-system +(KPSS) timer IPQ8064 devicetree section. Also, add a fixed-clock +description of SLEEP_CLK, which will do for now. + +Signed-off-by: Josh Cartwright +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -60,6 +60,14 @@ + }; + }; + ++ clocks { ++ sleep_clk: sleep_clk { ++ compatible = "fixed-clock"; ++ clock-frequency = <32768>; ++ #clock-cells = <0>; ++ }; ++ }; ++ + soc: soc { + #address-cells = <1>; + #size-cells = <1>; +@@ -89,10 +97,14 @@ + compatible = "qcom,kpss-timer", "qcom,msm-timer"; + interrupts = <1 1 0x301>, + <1 2 0x301>, +- <1 3 0x301>; ++ <1 3 0x301>, ++ <1 4 0x301>, ++ <1 5 0x301>; + reg = <0x0200a000 0x100>; + clock-frequency = <25000000>, + <32768>; ++ clocks = <&sleep_clk>; ++ clock-names = "sleep"; + cpu-offset = <0x80000>; + }; + diff --git a/target/linux/ipq806x/patches-3.18/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch b/target/linux/ipq806x/patches-3.18/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch new file mode 100644 index 0000000..6876768 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/013-ARM-msm-add-watchdog-entries-to-DT-timer-binding-doc.patch @@ -0,0 +1,50 @@ +From e535f01dffb6dd9e09934fa219be52af3437a8f6 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 19 Feb 2015 20:36:27 -0800 +Subject: [PATCH 3/3] ARM: msm: add watchdog entries to DT timer binding doc + +The watchdog has been reworked to use the same DT node as the timer. +This change is updating the device tree doc accordingly. + +Signed-off-by: Mathieu Olivari +--- + Documentation/devicetree/bindings/arm/msm/timer.txt | 16 +++++++++++++--- + 1 file changed, 13 insertions(+), 3 deletions(-) + +--- a/Documentation/devicetree/bindings/arm/msm/timer.txt ++++ b/Documentation/devicetree/bindings/arm/msm/timer.txt +@@ -9,11 +9,17 @@ Properties: + "qcom,scss-timer" - scorpion subsystem + + - interrupts : Interrupts for the the debug timer, the first general purpose +- timer, and optionally a second general purpose timer in that +- order. ++ timer, and optionally a second general purpose timer, and ++ optionally as well, 2 watchdog interrupts, in that order. + + - reg : Specifies the base address of the timer registers. + ++- clocks: Reference to the parent clocks, one per output clock. The parents ++ must appear in the same order as the clock names. ++ ++- clock-names: The name of the clocks as free-form strings. They should be in ++ the same order as the clocks. ++ + - clock-frequency : The frequency of the debug timer and the general purpose + timer(s) in Hz in that order. + +@@ -29,9 +35,13 @@ Example: + compatible = "qcom,scss-timer", "qcom,msm-timer"; + interrupts = <1 1 0x301>, + <1 2 0x301>, +- <1 3 0x301>; ++ <1 3 0x301>, ++ <1 4 0x301>, ++ <1 5 0x301>; + reg = <0x0200a000 0x100>; + clock-frequency = <19200000>, + <32768>; ++ clocks = <&sleep_clk>; ++ clock-names = "sleep"; + cpu-offset = <0x40000>; + }; diff --git a/target/linux/ipq806x/patches-3.18/020-add-ap148-bootargs.patch b/target/linux/ipq806x/patches-3.18/020-add-ap148-bootargs.patch new file mode 100644 index 0000000..a61481e --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/020-add-ap148-bootargs.patch @@ -0,0 +1,46 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -14,6 +14,14 @@ + }; + }; + ++ alias { ++ serial0 = &uart4; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ + soc { + pinmux@800000 { + i2c4_pins: i2c4_pinmux { +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -140,7 +140,7 @@ + ranges; + status = "disabled"; + +- serial@12490000 { ++ uart2: serial@12490000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x12490000 0x1000>, + <0x12480000 0x1000>; +@@ -175,7 +175,7 @@ + ranges; + status = "disabled"; + +- serial@16340000 { ++ uart4: serial@16340000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x16340000 0x1000>, + <0x16300000 0x1000>; +@@ -209,7 +209,7 @@ + ranges; + status = "disabled"; + +- serial@1a240000 { ++ uart5: serial@1a240000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x1a240000 0x1000>, + <0x1a200000 0x1000>; diff --git a/target/linux/ipq806x/patches-3.18/021-add-ap148-partitions.patch b/target/linux/ipq806x/patches-3.18/021-add-ap148-partitions.patch new file mode 100644 index 0000000..bfdb30f --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/021-add-ap148-partitions.patch @@ -0,0 +1,19 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -77,15 +77,7 @@ + spi-max-frequency = <50000000>; + reg = <0>; + +- partition@0 { +- label = "rootfs"; +- reg = <0x0 0x1000000>; +- }; +- +- partition@1 { +- label = "scratch"; +- reg = <0x1000000 0x1000000>; +- }; ++ linux,part-probe = "qcom-smem"; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-3.18/022-add-db149-dts.patch b/target/linux/ipq806x/patches-3.18/022-add-db149-dts.patch new file mode 100644 index 0000000..bd6ec1e --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/022-add-db149-dts.patch @@ -0,0 +1,160 @@ +From a32d6e7c8fca6371a2614924b89981bc912b6378 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Tue, 7 Apr 2015 19:58:58 -0700 +Subject: [PATCH] ARM: dts: qcom: add initial DB149 device-tree + +Add basic DB149 (IPQ806x based platform) device-tree. It supports UART, +SATA, USB2, USB3 and NOR flash. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 257 +++++++++++++++++++++++++++++++ + 2 files changed, 258 insertions(+) + create mode 100644 arch/arm/boot/dts/qcom-ipq8064-db149.dts + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -360,6 +360,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \ + qcom-apq8084-ifc6540.dtb \ + qcom-apq8084-mtp.dtb \ + qcom-ipq8064-ap148.dtb \ ++ qcom-ipq8064-db149.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -0,0 +1,132 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++/ { ++ model = "Qualcomm IPQ8064/DB149"; ++ compatible = "qcom,ipq8064-db149", "qcom,ipq8064"; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ alias { ++ serial0 = &uart2; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ spi_pins: spi_pins { ++ mux { ++ pins = "gpio18", "gpio19", "gpio21"; ++ function = "gsbi5"; ++ drive-strength = <10>; ++ bias-none; ++ }; ++ }; ++ }; ++ ++ gsbi2: gsbi@12480000 { ++ qcom,mode = ; ++ status = "ok"; ++ uart2: serial@12490000 { ++ status = "ok"; ++ }; ++ }; ++ ++ gsbi5: gsbi@1a200000 { ++ qcom,mode = ; ++ status = "ok"; ++ ++ spi4: spi@1a280000 { ++ status = "ok"; ++ spi-max-frequency = <50000000>; ++ ++ pinctrl-0 = <&spi_pins>; ++ pinctrl-names = "default"; ++ ++ cs-gpios = <&qcom_pinmux 20 0>; ++ ++ flash: m25p80@0 { ++ compatible = "s25fl256s1"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ spi-max-frequency = <50000000>; ++ reg = <0>; ++ m25p,fast-read; ++ ++ partition@0 { ++ label = "lowlevel_init"; ++ reg = <0x0 0x1b0000>; ++ }; ++ ++ partition@1 { ++ label = "u-boot"; ++ reg = <0x1b0000 0x80000>; ++ }; ++ ++ partition@2 { ++ label = "u-boot-env"; ++ reg = <0x230000 0x40000>; ++ }; ++ ++ partition@3 { ++ label = "caldata"; ++ reg = <0x270000 0x40000>; ++ }; ++ ++ partition@4 { ++ label = "firmware"; ++ reg = <0x2b0000 0x1d50000>; ++ }; ++ }; ++ }; ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ }; ++}; diff --git a/target/linux/ipq806x/patches-3.18/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch b/target/linux/ipq806x/patches-3.18/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch new file mode 100644 index 0000000..319859b --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch @@ -0,0 +1,53 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -46,15 +46,12 @@ + serial@16340000 { + status = "ok"; + }; +- +- i2c4: i2c@16380000 { +- status = "ok"; +- +- clock-frequency = <200000>; +- +- pinctrl-0 = <&i2c4_pins>; +- pinctrl-names = "default"; +- }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ + }; + + gsbi5: gsbi@1a200000 { +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -794,7 +794,7 @@ static struct clk_rcg gsbi7_qup_src = { + .parent_names = gcc_pxo_pll8, + .num_parents = 2, + .ops = &clk_rcg_ops, +- .flags = CLK_SET_PARENT_GATE, ++ .flags = CLK_SET_PARENT_GATE | CLK_IGNORE_UNUSED, + }, + }, + }; +@@ -810,7 +810,7 @@ static struct clk_branch gsbi7_qup_clk = + .parent_names = (const char *[]){ "gsbi7_qup_src" }, + .num_parents = 1, + .ops = &clk_branch_ops, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + }, + }, + }; +@@ -858,7 +858,7 @@ static struct clk_branch gsbi4_h_clk = { + .hw.init = &(struct clk_init_data){ + .name = "gsbi4_h_clk", + .ops = &clk_branch_ops, +- .flags = CLK_IS_ROOT, ++ .flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED, + }, + }, + }; diff --git a/target/linux/ipq806x/patches-3.18/024-ap148-add-memory-node.patch b/target/linux/ipq806x/patches-3.18/024-ap148-add-memory-node.patch new file mode 100644 index 0000000..f026ed9 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/024-ap148-add-memory-node.patch @@ -0,0 +1,14 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -4,6 +4,11 @@ + model = "Qualcomm IPQ8064/AP148"; + compatible = "qcom,ipq8064-ap148", "qcom,ipq8064"; + ++ memory@0 { ++ reg = <0x42000000 0x1e000000>; ++ device_type = "memory"; ++ }; ++ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; diff --git a/target/linux/ipq806x/patches-3.18/030-hwspinlock-core-add-device-tree-support.patch b/target/linux/ipq806x/patches-3.18/030-hwspinlock-core-add-device-tree-support.patch new file mode 100644 index 0000000..04f35b7 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/030-hwspinlock-core-add-device-tree-support.patch @@ -0,0 +1,167 @@ +From fb7737e949e31d8a71acee6bbb670f32dbd2a2c0 Mon Sep 17 00:00:00 2001 +From: Suman Anna +Date: Wed, 4 Mar 2015 20:01:14 -0600 +Subject: [PATCH] hwspinlock/core: add device tree support + +This patch adds a new OF-friendly API of_hwspin_lock_get_id() +for hwspinlock clients to use/request locks from a hwspinlock +device instantiated through a device-tree blob. This new API +can be used by hwspinlock clients to get the id for a specific +lock using the phandle + args specifier, so that it can be +requested using the available hwspin_lock_request_specific() +API. + +Signed-off-by: Suman Anna +Reviewed-by: Bjorn Andersson +[small comment clarification] +Signed-off-by: Ohad Ben-Cohen +--- + Documentation/hwspinlock.txt | 10 +++++ + drivers/hwspinlock/hwspinlock_core.c | 79 ++++++++++++++++++++++++++++++++++++ + include/linux/hwspinlock.h | 7 ++++ + 3 files changed, 96 insertions(+) + +--- a/Documentation/hwspinlock.txt ++++ b/Documentation/hwspinlock.txt +@@ -48,6 +48,16 @@ independent, drivers. + ids for predefined purposes. + Should be called from a process context (might sleep). + ++ int of_hwspin_lock_get_id(struct device_node *np, int index); ++ - retrieve the global lock id for an OF phandle-based specific lock. ++ This function provides a means for DT users of a hwspinlock module ++ to get the global lock id of a specific hwspinlock, so that it can ++ be requested using the normal hwspin_lock_request_specific() API. ++ The function returns a lock id number on success, -EPROBE_DEFER if ++ the hwspinlock device is not yet registered with the core, or other ++ error values. ++ Should be called from a process context (might sleep). ++ + int hwspin_lock_free(struct hwspinlock *hwlock); + - free a previously-assigned hwspinlock; returns 0 on success, or an + appropriate error code on failure (e.g. -EINVAL if the hwspinlock +--- a/drivers/hwspinlock/hwspinlock_core.c ++++ b/drivers/hwspinlock/hwspinlock_core.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + + #include "hwspinlock_internal.h" + +@@ -257,6 +258,84 @@ void __hwspin_unlock(struct hwspinlock * + } + EXPORT_SYMBOL_GPL(__hwspin_unlock); + ++/** ++ * of_hwspin_lock_simple_xlate - translate hwlock_spec to return a lock id ++ * @bank: the hwspinlock device bank ++ * @hwlock_spec: hwlock specifier as found in the device tree ++ * ++ * This is a simple translation function, suitable for hwspinlock platform ++ * drivers that only has a lock specifier length of 1. ++ * ++ * Returns a relative index of the lock within a specified bank on success, ++ * or -EINVAL on invalid specifier cell count. ++ */ ++static inline int ++of_hwspin_lock_simple_xlate(const struct of_phandle_args *hwlock_spec) ++{ ++ if (WARN_ON(hwlock_spec->args_count != 1)) ++ return -EINVAL; ++ ++ return hwlock_spec->args[0]; ++} ++ ++/** ++ * of_hwspin_lock_get_id() - get lock id for an OF phandle-based specific lock ++ * @np: device node from which to request the specific hwlock ++ * @index: index of the hwlock in the list of values ++ * ++ * This function provides a means for DT users of the hwspinlock module to ++ * get the global lock id of a specific hwspinlock using the phandle of the ++ * hwspinlock device, so that it can be requested using the normal ++ * hwspin_lock_request_specific() API. ++ * ++ * Returns the global lock id number on success, -EPROBE_DEFER if the hwspinlock ++ * device is not yet registered, -EINVAL on invalid args specifier value or an ++ * appropriate error as returned from the OF parsing of the DT client node. ++ */ ++int of_hwspin_lock_get_id(struct device_node *np, int index) ++{ ++ struct of_phandle_args args; ++ struct hwspinlock *hwlock; ++ struct radix_tree_iter iter; ++ void **slot; ++ int id; ++ int ret; ++ ++ ret = of_parse_phandle_with_args(np, "hwlocks", "#hwlock-cells", index, ++ &args); ++ if (ret) ++ return ret; ++ ++ /* Find the hwspinlock device: we need its base_id */ ++ ret = -EPROBE_DEFER; ++ rcu_read_lock(); ++ radix_tree_for_each_slot(slot, &hwspinlock_tree, &iter, 0) { ++ hwlock = radix_tree_deref_slot(slot); ++ if (unlikely(!hwlock)) ++ continue; ++ ++ if (hwlock->bank->dev->of_node == args.np) { ++ ret = 0; ++ break; ++ } ++ } ++ rcu_read_unlock(); ++ if (ret < 0) ++ goto out; ++ ++ id = of_hwspin_lock_simple_xlate(&args); ++ if (id < 0 || id >= hwlock->bank->num_locks) { ++ ret = -EINVAL; ++ goto out; ++ } ++ id += hwlock->bank->base_id; ++ ++out: ++ of_node_put(args.np); ++ return ret ? ret : id; ++} ++EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id); ++ + static int hwspin_lock_register_single(struct hwspinlock *hwlock, int id) + { + struct hwspinlock *tmp; +--- a/include/linux/hwspinlock.h ++++ b/include/linux/hwspinlock.h +@@ -26,6 +26,7 @@ + #define HWLOCK_IRQ 0x02 /* Disable interrupts, don't save state */ + + struct device; ++struct device_node; + struct hwspinlock; + struct hwspinlock_device; + struct hwspinlock_ops; +@@ -66,6 +67,7 @@ int hwspin_lock_unregister(struct hwspin + struct hwspinlock *hwspin_lock_request(void); + struct hwspinlock *hwspin_lock_request_specific(unsigned int id); + int hwspin_lock_free(struct hwspinlock *hwlock); ++int of_hwspin_lock_get_id(struct device_node *np, int index); + int hwspin_lock_get_id(struct hwspinlock *hwlock); + int __hwspin_lock_timeout(struct hwspinlock *, unsigned int, int, + unsigned long *); +@@ -120,6 +122,11 @@ void __hwspin_unlock(struct hwspinlock * + { + } + ++static inline int of_hwspin_lock_get_id(struct device_node *np, int index) ++{ ++ return 0; ++} ++ + static inline int hwspin_lock_get_id(struct hwspinlock *hwlock) + { + return 0; diff --git a/target/linux/ipq806x/patches-3.18/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch b/target/linux/ipq806x/patches-3.18/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch new file mode 100644 index 0000000..581b199 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch @@ -0,0 +1,234 @@ +From 19a0f61224d2d91860fa8291ab63cb104ee86bdd Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Tue, 24 Mar 2015 10:11:05 -0700 +Subject: [PATCH] hwspinlock: qcom: Add support for Qualcomm HW Mutex block + +Add driver for Qualcomm Hardware Mutex block found in many Qualcomm +SoCs. + +Based on initial effort by Kumar Gala + +Signed-off-by: Bjorn Andersson +Reviewed-by: Andy Gross +Reviewed-by: Jeffrey Hugo +Signed-off-by: Ohad Ben-Cohen +--- + drivers/hwspinlock/Kconfig | 12 +++ + drivers/hwspinlock/Makefile | 1 + + drivers/hwspinlock/qcom_hwspinlock.c | 181 +++++++++++++++++++++++++++++++++++ + 3 files changed, 194 insertions(+) + create mode 100644 drivers/hwspinlock/qcom_hwspinlock.c + +--- a/drivers/hwspinlock/Kconfig ++++ b/drivers/hwspinlock/Kconfig +@@ -18,6 +18,18 @@ config HWSPINLOCK_OMAP + + If unsure, say N. + ++config HWSPINLOCK_QCOM ++ tristate "Qualcomm Hardware Spinlock device" ++ depends on ARCH_QCOM ++ select HWSPINLOCK ++ select MFD_SYSCON ++ help ++ Say y here to support the Qualcomm Hardware Mutex functionality, which ++ provides a synchronisation mechanism for the various processors on ++ the SoC. ++ ++ If unsure, say N. ++ + config HSEM_U8500 + tristate "STE Hardware Semaphore functionality" + depends on ARCH_U8500 +--- a/drivers/hwspinlock/Makefile ++++ b/drivers/hwspinlock/Makefile +@@ -4,4 +4,5 @@ + + obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o + obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o ++obj-$(CONFIG_HWSPINLOCK_QCOM) += qcom_hwspinlock.o + obj-$(CONFIG_HSEM_U8500) += u8500_hsem.o +--- /dev/null ++++ b/drivers/hwspinlock/qcom_hwspinlock.c +@@ -0,0 +1,181 @@ ++/* ++ * Copyright (c) 2013, The Linux Foundation. All rights reserved. ++ * Copyright (c) 2015, Sony Mobile Communications AB ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "hwspinlock_internal.h" ++ ++#define QCOM_MUTEX_APPS_PROC_ID 1 ++#define QCOM_MUTEX_NUM_LOCKS 32 ++ ++static int qcom_hwspinlock_trylock(struct hwspinlock *lock) ++{ ++ struct regmap_field *field = lock->priv; ++ u32 lock_owner; ++ int ret; ++ ++ ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID); ++ if (ret) ++ return ret; ++ ++ ret = regmap_field_read(field, &lock_owner); ++ if (ret) ++ return ret; ++ ++ return lock_owner == QCOM_MUTEX_APPS_PROC_ID; ++} ++ ++static void qcom_hwspinlock_unlock(struct hwspinlock *lock) ++{ ++ struct regmap_field *field = lock->priv; ++ u32 lock_owner; ++ int ret; ++ ++ ret = regmap_field_read(field, &lock_owner); ++ if (ret) { ++ pr_err("%s: unable to query spinlock owner\n", __func__); ++ return; ++ } ++ ++ if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) { ++ pr_err("%s: spinlock not owned by us (actual owner is %d)\n", ++ __func__, lock_owner); ++ } ++ ++ ret = regmap_field_write(field, 0); ++ if (ret) ++ pr_err("%s: failed to unlock spinlock\n", __func__); ++} ++ ++static const struct hwspinlock_ops qcom_hwspinlock_ops = { ++ .trylock = qcom_hwspinlock_trylock, ++ .unlock = qcom_hwspinlock_unlock, ++}; ++ ++static const struct of_device_id qcom_hwspinlock_of_match[] = { ++ { .compatible = "qcom,sfpb-mutex" }, ++ { .compatible = "qcom,tcsr-mutex" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); ++ ++static int qcom_hwspinlock_probe(struct platform_device *pdev) ++{ ++ struct hwspinlock_device *bank; ++ struct device_node *syscon; ++ struct reg_field field; ++ struct regmap *regmap; ++ size_t array_size; ++ u32 stride; ++ u32 base; ++ int ret; ++ int i; ++ ++ syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0); ++ if (!syscon) { ++ dev_err(&pdev->dev, "no syscon property\n"); ++ return -ENODEV; ++ } ++ ++ regmap = syscon_node_to_regmap(syscon); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, &base); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "no offset in syscon\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, &stride); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "no stride syscon\n"); ++ return -EINVAL; ++ } ++ ++ array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); ++ bank = devm_kzalloc(&pdev->dev, sizeof(*bank) + array_size, GFP_KERNEL); ++ if (!bank) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, bank); ++ ++ for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { ++ field.reg = base + i * stride; ++ field.lsb = 0; ++ field.msb = 32; ++ ++ bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, ++ regmap, field); ++ } ++ ++ pm_runtime_enable(&pdev->dev); ++ ++ ret = hwspin_lock_register(bank, &pdev->dev, &qcom_hwspinlock_ops, ++ 0, QCOM_MUTEX_NUM_LOCKS); ++ if (ret) ++ pm_runtime_disable(&pdev->dev); ++ ++ return ret; ++} ++ ++static int qcom_hwspinlock_remove(struct platform_device *pdev) ++{ ++ struct hwspinlock_device *bank = platform_get_drvdata(pdev); ++ int ret; ++ ++ ret = hwspin_lock_unregister(bank); ++ if (ret) { ++ dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); ++ return ret; ++ } ++ ++ pm_runtime_disable(&pdev->dev); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_hwspinlock_driver = { ++ .probe = qcom_hwspinlock_probe, ++ .remove = qcom_hwspinlock_remove, ++ .driver = { ++ .name = "qcom_hwspinlock", ++ .of_match_table = qcom_hwspinlock_of_match, ++ }, ++}; ++ ++static int __init qcom_hwspinlock_init(void) ++{ ++ return platform_driver_register(&qcom_hwspinlock_driver); ++} ++/* board init code might need to reserve hwspinlocks for predefined purposes */ ++postcore_initcall(qcom_hwspinlock_init); ++ ++static void __exit qcom_hwspinlock_exit(void) ++{ ++ platform_driver_unregister(&qcom_hwspinlock_driver); ++} ++module_exit(qcom_hwspinlock_exit); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs"); diff --git a/target/linux/ipq806x/patches-3.18/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch b/target/linux/ipq806x/patches-3.18/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch new file mode 100644 index 0000000..70d5a58 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch @@ -0,0 +1,26 @@ +From bd5717a4632cdecafe82d03de7dcb3b1876e2828 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Fri, 26 Jun 2015 14:47:21 -0700 +Subject: [PATCH] hwspinlock: qcom: Correct msb in regmap_field + +msb of the regmap_field was mistakenly given the value 32, to set all bits +in the regmap update mask; although incorrect this worked until 921cc294, +where the mask calculation was corrected. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Ohad Ben-Cohen +--- + drivers/hwspinlock/qcom_hwspinlock.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/hwspinlock/qcom_hwspinlock.c ++++ b/drivers/hwspinlock/qcom_hwspinlock.c +@@ -123,7 +123,7 @@ static int qcom_hwspinlock_probe(struct + for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { + field.reg = base + i * stride; + field.lsb = 0; +- field.msb = 32; ++ field.msb = 31; + + bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, + regmap, field); diff --git a/target/linux/ipq806x/patches-3.18/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-3.18/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch new file mode 100644 index 0000000..4a35325 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch @@ -0,0 +1,34 @@ +From c7c482da19a5e4ba7101198c21c2434056b0b2da Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 13 Aug 2015 09:45:26 -0700 +Subject: [PATCH 1/3] ARM: qcom: add SFPB nodes to IPQ806x dts + +Add one new node to the ipq806x.dtsi file to declare & register the +hardware spinlock devices. This mechanism is required to be used by +other drivers such as SMEM. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -291,5 +291,17 @@ + #clock-cells = <1>; + #reset-cells = <1>; + }; ++ ++ sfpb_mutex_block: syscon@1200600 { ++ compatible = "syscon"; ++ reg = <0x01200600 0x100>; ++ }; + }; ++ ++ sfpb_mutex: sfpb-mutex { ++ compatible = "qcom,sfpb-mutex"; ++ syscon = <&sfpb_mutex_block 4 4>; ++ ++ #hwlock-cells = <1>; ++ }; + }; diff --git a/target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch b/target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch new file mode 100644 index 0000000..d22db22 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,1/2] soc: qcom: Add device tree binding for SMEM +From: Bjorn Andersson +X-Patchwork-Id: 6202201 +Message-Id: <1428795178-24312-1-git-send-email-bjorn.andersson@sonymobile.com> +To: Rob Herring , Pawel Moll , + Mark Rutland , + Ian Campbell , + Kumar Gala , Jeffrey Hugo , + Andry Gross +Cc: , + linux-arm-msm , + +Date: Sat, 11 Apr 2015 16:32:57 -0700 + +Add device tree binding documentation for the Qualcom Shared Memory +manager. + +Signed-off-by: Bjorn Andersson + +--- +Changes since v1: +- None + + .../devicetree/bindings/soc/qcom/qcom,smem.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt +@@ -0,0 +1,49 @@ ++Qualcomm Shared Memory binding ++ ++This binding describes the Qualcomm Shared Memory, used to share data between ++various subsystems and OSes in Qualcomm platforms. ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be: ++ "qcom,smem" ++ ++- memory-region: ++ Usage: required ++ Value type: ++ Definition: handle to memory reservation for main smem memory region. ++ ++- reg: ++ Usage: optional ++ Value type: ++ Definition: base address and size pair for any additional memory areas ++ of the shared memory. ++ ++- hwspinlocks: ++ Usage: required ++ Value type: ++ Definition: reference to a hwspinlock used to protect allocations from ++ the shared memory ++ ++= EXAMPLE ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ ++ smem_region: smem@fa00000 { ++ reg = <0xfa00000 0x200000>; ++ no-map; ++ }; ++ }; ++ ++ smem@fa00000 { ++ compatible = "qcom,smem"; ++ ++ memory-region = <&smem_region>; ++ reg = <0xfc428000 0x4000>; ++ ++ hwlocks = <&tcsr_mutex 3>; ++ }; diff --git a/target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch b/target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch new file mode 100644 index 0000000..c8cff1a --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch @@ -0,0 +1,841 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,2/2] soc: qcom: Add Shared Memory Manager driver +From: Bjorn Andersson +X-Patchwork-Id: 6202211 +Message-Id: <1428795178-24312-2-git-send-email-bjorn.andersson@sonymobile.com> +To: Kumar Gala , Andy Gross , + David Brown , Jeffrey Hugo +Cc: , , + +Date: Sat, 11 Apr 2015 16:32:58 -0700 + +The Shared Memory Manager driver implements an interface for allocating +and accessing items in the memory area shared among all of the +processors in a Qualcomm platform. + +Signed-off-by: Bjorn Andersson +Reviewed-by: Andy Gross +Tested-by: Andy Gross + +--- +Changes since v1: +- ioremapping the regions nocache +- improved documentation of the two regions of partitions +- corrected free space check in private allocator + + drivers/soc/qcom/Kconfig | 7 + + drivers/soc/qcom/Makefile | 1 + + drivers/soc/qcom/smem.c | 768 ++++++++++++++++++++++++++++++++++++++++++ + include/linux/soc/qcom/smem.h | 14 + + 4 files changed, 790 insertions(+) + create mode 100644 drivers/soc/qcom/smem.c + create mode 100644 include/linux/soc/qcom/smem.h + +--- a/drivers/soc/qcom/Kconfig ++++ b/drivers/soc/qcom/Kconfig +@@ -9,3 +9,10 @@ config QCOM_GSBI + functions for connecting the underlying serial UART, SPI, and I2C + devices to the output pins. + ++config QCOM_SMEM ++ tristate "Qualcomm Shared Memory Manager (SMEM)" ++ depends on ARCH_QCOM ++ help ++ Say y here to enable support for the Qualcomm Shared Memory Manager. ++ The driver provides an interface to items in a heap shared among all ++ processors in a Qualcomm platform. +--- a/drivers/soc/qcom/Makefile ++++ b/drivers/soc/qcom/Makefile +@@ -1 +1,2 @@ + obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o ++obj-$(CONFIG_QCOM_SMEM) += smem.o +--- /dev/null ++++ b/drivers/soc/qcom/smem.c +@@ -0,0 +1,768 @@ ++/* ++ * Copyright (c) 2015, Sony Mobile Communications AB. ++ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * The Qualcomm shared memory system is a allocate only heap structure that ++ * consists of one of more memory areas that can be accessed by the processors ++ * in the SoC. ++ * ++ * All systems contains a global heap, accessible by all processors in the SoC, ++ * with a table of contents data structure (@smem_header) at the beginning of ++ * the main shared memory block. ++ * ++ * The global header contains metadata for allocations as well as a fixed list ++ * of 512 entries (@smem_global_entry) that can be initialized to reference ++ * parts of the shared memory space. ++ * ++ * ++ * In addition to this global heap a set of "private" heaps can be set up at ++ * boot time with access restrictions so that only certain processor pairs can ++ * access the data. ++ * ++ * These partitions are referenced from an optional partition table ++ * (@smem_ptable), that is found 4kB from the end of the main smem region. The ++ * partition table entries (@smem_ptable_entry) lists the involved processors ++ * (or hosts) and their location in the main shared memory region. ++ * ++ * Each partition starts with a header (@smem_partition_header) that identifies ++ * the partition and holds properties for the two internal memory regions. The ++ * two regions are cached and non-cached memory respectively. Each region ++ * contain a link list of allocation headers (@smem_private_entry) followed by ++ * their data. ++ * ++ * Items in the non-cached region are allocated from the start of the partition ++ * while items in the cached region are allocated from the end. The free area ++ * is hence the region between the cached and non-cached offsets. ++ * ++ * ++ * To synchronize allocations in the shared memory heaps a remote spinlock must ++ * be held - currently lock number 3 of the sfpb or tcsr is used for this on all ++ * platforms. ++ * ++ */ ++ ++/** ++ * struct smem_proc_comm - proc_comm communication struct (legacy) ++ * @command: current command to be executed ++ * @status: status of the currently requested command ++ * @params: parameters to the command ++ */ ++struct smem_proc_comm { ++ u32 command; ++ u32 status; ++ u32 params[2]; ++}; ++ ++/** ++ * struct smem_global_entry - entry to reference smem items on the heap ++ * @allocated: boolean to indicate if this entry is used ++ * @offset: offset to the allocated space ++ * @size: size of the allocated space, 8 byte aligned ++ * @aux_base: base address for the memory region used by this unit, or 0 for ++ * the default region. bits 0,1 are reserved ++ */ ++struct smem_global_entry { ++ u32 allocated; ++ u32 offset; ++ u32 size; ++ u32 aux_base; /* bits 1:0 reserved */ ++}; ++#define AUX_BASE_MASK 0xfffffffc ++ ++/** ++ * struct smem_header - header found in beginning of primary smem region ++ * @proc_comm: proc_comm communication interface (legacy) ++ * @version: array of versions for the various subsystems ++ * @initialized: boolean to indicate that smem is initialized ++ * @free_offset: index of the first unallocated byte in smem ++ * @available: number of bytes available for allocation ++ * @reserved: reserved field, must be 0 ++ * toc: array of references to items ++ */ ++struct smem_header { ++ struct smem_proc_comm proc_comm[4]; ++ u32 version[32]; ++ u32 initialized; ++ u32 free_offset; ++ u32 available; ++ u32 reserved; ++ struct smem_global_entry toc[]; ++}; ++ ++/** ++ * struct smem_ptable_entry - one entry in the @smem_ptable list ++ * @offset: offset, within the main shared memory region, of the partition ++ * @size: size of the partition ++ * @flags: flags for the partition (currently unused) ++ * @host0: first processor/host with access to this partition ++ * @host1: second processor/host with access to this partition ++ * @reserved: reserved entries for later use ++ */ ++struct smem_ptable_entry { ++ u32 offset; ++ u32 size; ++ u32 flags; ++ u16 host0; ++ u16 host1; ++ u32 reserved[8]; ++}; ++ ++/** ++ * struct smem_ptable - partition table for the private partitions ++ * @magic: magic number, must be SMEM_PTABLE_MAGIC ++ * @version: version of the partition table ++ * @num_entries: number of partitions in the table ++ * @reserved: for now reserved entries ++ * @entry: list of @smem_ptable_entry for the @num_entries partitions ++ */ ++struct smem_ptable { ++ u32 magic; ++ u32 version; ++ u32 num_entries; ++ u32 reserved[5]; ++ struct smem_ptable_entry entry[]; ++}; ++#define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */ ++ ++/** ++ * struct smem_partition_header - header of the partitions ++ * @magic: magic number, must be SMEM_PART_MAGIC ++ * @host0: first processor/host with access to this partition ++ * @host1: second processor/host with access to this partition ++ * @size: size of the partition ++ * @offset_free_uncached: offset to the first free byte of uncached memory in ++ * this partition ++ * @offset_free_cached: offset to the first free byte of cached memory in this ++ * partition ++ * @reserved: for now reserved entries ++ */ ++struct smem_partition_header { ++ u32 magic; ++ u16 host0; ++ u16 host1; ++ u32 size; ++ u32 offset_free_uncached; ++ u32 offset_free_cached; ++ u32 reserved[3]; ++}; ++#define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */ ++ ++/** ++ * struct smem_private_entry - header of each item in the private partition ++ * @canary: magic number, must be SMEM_PRIVATE_CANARY ++ * @item: identifying number of the smem item ++ * @size: size of the data, including padding bytes ++ * @padding_data: number of bytes of padding of data ++ * @padding_hdr: number of bytes of padding between the header and the data ++ * @reserved: for now reserved entry ++ */ ++struct smem_private_entry { ++ u16 canary; ++ u16 item; ++ u32 size; /* includes padding bytes */ ++ u16 padding_data; ++ u16 padding_hdr; ++ u32 reserved; ++}; ++#define SMEM_PRIVATE_CANARY 0xa5a5 ++ ++/* ++ * Item 3 of the global heap contains an array of versions for the various ++ * software components in the SoC. We verify that the boot loader version is ++ * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check. ++ */ ++#define SMEM_ITEM_VERSION 3 ++#define SMEM_MASTER_SBL_VERSION_INDEX 7 ++#define SMEM_EXPECTED_VERSION 11 ++ ++/* ++ * The first 8 items are only to be allocated by the boot loader while ++ * initializing the heap. ++ */ ++#define SMEM_ITEM_LAST_FIXED 8 ++ ++/* Highest accepted item number, for both global and private heaps */ ++#define SMEM_ITEM_LAST 512 ++ ++/* Processor/host identifier for the application processor */ ++#define SMEM_HOST_APPS 0 ++ ++/* Max number of processors/hosts in a system */ ++#define SMEM_HOST_COUNT 7 ++ ++/** ++ * struct smem_region - representation of a chunk of memory used for smem ++ * @aux_base: identifier of aux_mem base ++ * @virt_base: virtual base address of memory with this aux_mem identifier ++ * @size: size of the memory region ++ */ ++struct smem_region { ++ u32 aux_base; ++ void __iomem *virt_base; ++ size_t size; ++}; ++ ++/** ++ * struct qcom_smem - device data for the smem device ++ * @dev: device pointer ++ * @hwlock: reference to a hwspinlock ++ * @partitions: list of pointers to partitions affecting the current ++ * processor/host ++ * @num_regions: number of @regions ++ * @regions: list of the memory regions defining the shared memory ++ */ ++struct qcom_smem { ++ struct device *dev; ++ ++ struct hwspinlock *hwlock; ++ ++ struct smem_partition_header *partitions[SMEM_HOST_COUNT]; ++ ++ unsigned num_regions; ++ struct smem_region regions[0]; ++}; ++ ++/* Pointer to the one and only smem handle */ ++static struct qcom_smem *__smem; ++ ++/* Timeout (ms) for the trylock of remote spinlocks */ ++#define HWSPINLOCK_TIMEOUT 1000 ++ ++static int qcom_smem_alloc_private(struct qcom_smem *smem, ++ unsigned host, ++ unsigned item, ++ size_t size) ++{ ++ struct smem_partition_header *phdr; ++ struct smem_private_entry *hdr; ++ size_t alloc_size; ++ void *p; ++ ++ /* We're not going to find it if there's no matching partition */ ++ if (host >= SMEM_HOST_COUNT || !smem->partitions[host]) ++ return -ENOENT; ++ ++ phdr = smem->partitions[host]; ++ ++ p = (void *)phdr + sizeof(*phdr); ++ while (p < (void *)phdr + phdr->offset_free_uncached) { ++ hdr = p; ++ ++ if (hdr->canary != SMEM_PRIVATE_CANARY) { ++ dev_err(smem->dev, ++ "Found invalid canary in host %d partition\n", ++ host); ++ return -EINVAL; ++ } ++ ++ if (hdr->item == item) ++ return -EEXIST; ++ ++ p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; ++ } ++ ++ /* Check that we don't grow into the cached region */ ++ alloc_size = sizeof(*hdr) + ALIGN(size, 8); ++ if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) { ++ dev_err(smem->dev, "Out of memory\n"); ++ return -ENOSPC; ++ } ++ ++ hdr = p; ++ hdr->canary = SMEM_PRIVATE_CANARY; ++ hdr->item = item; ++ hdr->size = ALIGN(size, 8); ++ hdr->padding_data = hdr->size - size; ++ hdr->padding_hdr = 0; ++ ++ /* ++ * Ensure the header is written before we advance the free offset, so ++ * that remote processors that does not take the remote spinlock still ++ * gets a consistent view of the linked list. ++ */ ++ wmb(); ++ phdr->offset_free_uncached += alloc_size; ++ ++ return 0; ++} ++ ++static int qcom_smem_alloc_global(struct qcom_smem *smem, ++ unsigned item, ++ size_t size) ++{ ++ struct smem_header *header; ++ struct smem_global_entry *entry; ++ ++ if (WARN_ON(item >= SMEM_ITEM_LAST)) ++ return -EINVAL; ++ ++ header = smem->regions[0].virt_base; ++ entry = &header->toc[item]; ++ if (entry->allocated) ++ return -EEXIST; ++ ++ size = ALIGN(size, 8); ++ if (WARN_ON(size > header->available)) ++ return -ENOMEM; ++ ++ entry->offset = header->free_offset; ++ entry->size = size; ++ ++ /* ++ * Ensure the header is consistent before we mark the item allocated, ++ * so that remote processors will get a consistent view of the item ++ * even though they do not take the spinlock on read. ++ */ ++ wmb(); ++ entry->allocated = 1; ++ ++ header->free_offset += size; ++ header->available -= size; ++ ++ return 0; ++} ++ ++/** ++ * qcom_smem_alloc - allocate space for a smem item ++ * @host: remote processor id, or -1 ++ * @item: smem item handle ++ * @size: number of bytes to be allocated ++ * ++ * Allocate space for a given smem item of size @size, given that the item is ++ * not yet allocated. ++ */ ++int qcom_smem_alloc(unsigned host, unsigned item, size_t size) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!__smem) ++ return -EPROBE_DEFER; ++ ++ if (item < SMEM_ITEM_LAST_FIXED) { ++ dev_err(__smem->dev, ++ "Rejecting allocation of static entry %d\n", item); ++ return -EINVAL; ++ } ++ ++ ret = hwspin_lock_timeout_irqsave(__smem->hwlock, ++ HWSPINLOCK_TIMEOUT, ++ &flags); ++ if (ret) ++ return ret; ++ ++ ret = qcom_smem_alloc_private(__smem, host, item, size); ++ if (ret == -ENOENT) ++ ret = qcom_smem_alloc_global(__smem, item, size); ++ ++ hwspin_unlock_irqrestore(__smem->hwlock, &flags); ++ ++ return ret; ++} ++EXPORT_SYMBOL(qcom_smem_alloc); ++ ++static int qcom_smem_get_global(struct qcom_smem *smem, ++ unsigned item, ++ void **ptr, ++ size_t *size) ++{ ++ struct smem_header *header; ++ struct smem_region *area; ++ struct smem_global_entry *entry; ++ u32 aux_base; ++ unsigned i; ++ ++ if (WARN_ON(item >= SMEM_ITEM_LAST)) ++ return -EINVAL; ++ ++ header = smem->regions[0].virt_base; ++ entry = &header->toc[item]; ++ if (!entry->allocated) ++ return -ENXIO; ++ ++ if (ptr != NULL) { ++ aux_base = entry->aux_base & AUX_BASE_MASK; ++ ++ for (i = 0; i < smem->num_regions; i++) { ++ area = &smem->regions[i]; ++ ++ if (area->aux_base == aux_base || !aux_base) { ++ *ptr = area->virt_base + entry->offset; ++ break; ++ } ++ } ++ } ++ if (size != NULL) ++ *size = entry->size; ++ ++ return 0; ++} ++ ++static int qcom_smem_get_private(struct qcom_smem *smem, ++ unsigned host, ++ unsigned item, ++ void **ptr, ++ size_t *size) ++{ ++ struct smem_partition_header *phdr; ++ struct smem_private_entry *hdr; ++ void *p; ++ ++ /* We're not going to find it if there's no matching partition */ ++ if (host >= SMEM_HOST_COUNT || !smem->partitions[host]) ++ return -ENOENT; ++ ++ phdr = smem->partitions[host]; ++ ++ p = (void *)phdr + sizeof(*phdr); ++ while (p < (void *)phdr + phdr->offset_free_uncached) { ++ hdr = p; ++ ++ if (hdr->canary != SMEM_PRIVATE_CANARY) { ++ dev_err(smem->dev, ++ "Found invalid canary in host %d partition\n", ++ host); ++ return -EINVAL; ++ } ++ ++ if (hdr->item == item) { ++ if (ptr != NULL) ++ *ptr = p + sizeof(*hdr) + hdr->padding_hdr; ++ ++ if (size != NULL) ++ *size = hdr->size - hdr->padding_data; ++ ++ return 0; ++ } ++ ++ p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; ++ } ++ ++ return -ENOENT; ++} ++ ++/** ++ * qcom_smem_get - resolve ptr of size of a smem item ++ * @host: the remote processor, or -1 ++ * @item: smem item handle ++ * @ptr: pointer to be filled out with address of the item ++ * @size: pointer to be filled out with size of the item ++ * ++ * Looks up pointer and size of a smem item. ++ */ ++int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!__smem) ++ return -EPROBE_DEFER; ++ ++ ret = hwspin_lock_timeout_irqsave(__smem->hwlock, ++ HWSPINLOCK_TIMEOUT, ++ &flags); ++ if (ret) ++ return ret; ++ ++ ret = qcom_smem_get_private(__smem, host, item, ptr, size); ++ if (ret == -ENOENT) ++ ret = qcom_smem_get_global(__smem, item, ptr, size); ++ ++ hwspin_unlock_irqrestore(__smem->hwlock, &flags); ++ return ret; ++ ++} ++EXPORT_SYMBOL(qcom_smem_get); ++ ++/** ++ * qcom_smem_get_free_space - retrieve amont of free space in a partition ++ * @host: the remote processor identifing a partition, or -1 ++ * ++ * To be used by smem clients as a quick way to determine if any new ++ * allocations has been made. ++ */ ++int qcom_smem_get_free_space(unsigned host) ++{ ++ struct smem_partition_header *phdr; ++ struct smem_header *header; ++ unsigned ret; ++ ++ if (!__smem) ++ return -EPROBE_DEFER; ++ ++ if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { ++ phdr = __smem->partitions[host]; ++ ret = phdr->offset_free_uncached; ++ } else { ++ header = __smem->regions[0].virt_base; ++ ret = header->available; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL(qcom_smem_get_free_space); ++ ++static int qcom_smem_get_sbl_version(struct qcom_smem *smem) ++{ ++ unsigned *versions; ++ size_t size; ++ int ret; ++ ++ ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, ++ (void **)&versions, &size); ++ if (ret < 0) { ++ dev_err(smem->dev, "Unable to read the version item\n"); ++ return -ENOENT; ++ } ++ ++ if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) { ++ dev_err(smem->dev, "Version item is too small\n"); ++ return -EINVAL; ++ } ++ ++ return versions[SMEM_MASTER_SBL_VERSION_INDEX]; ++} ++ ++static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, ++ unsigned local_host) ++{ ++ struct smem_partition_header *header; ++ struct smem_ptable_entry *entry; ++ struct smem_ptable *ptable; ++ unsigned remote_host; ++ int i; ++ ++ ptable = smem->regions[0].virt_base + smem->regions[0].size - 4 * 1024; ++ if (ptable->magic != SMEM_PTABLE_MAGIC) ++ return 0; ++ ++ if (ptable->version != 1) { ++ dev_err(smem->dev, ++ "Unsupported partition header version %d\n", ++ ptable->version); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < ptable->num_entries; i++) { ++ entry = &ptable->entry[i]; ++ ++ if (entry->host0 != local_host && entry->host1 != local_host) ++ continue; ++ ++ if (!entry->offset) ++ continue; ++ ++ if (!entry->size) ++ continue; ++ ++ if (entry->host0 == local_host) ++ remote_host = entry->host1; ++ else ++ remote_host = entry->host0; ++ ++ if (smem->partitions[remote_host]) { ++ dev_err(smem->dev, ++ "Already found a partition for host %d\n", ++ remote_host); ++ return -EINVAL; ++ } ++ ++ header = smem->regions[0].virt_base + entry->offset; ++ ++ if (header->magic != SMEM_PART_MAGIC) { ++ dev_err(smem->dev, ++ "Partition %d has invalid magic\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->host0 != local_host && header->host1 != local_host) { ++ dev_err(smem->dev, ++ "Partition %d hosts are invalid\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->host0 != remote_host && header->host1 != remote_host) { ++ dev_err(smem->dev, ++ "Partition %d hosts are invalid\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->size != entry->size) { ++ dev_err(smem->dev, ++ "Partition %d has invalid size\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->offset_free_uncached > header->size) { ++ dev_err(smem->dev, ++ "Partition %d has invalid free pointer\n", i); ++ return -EINVAL; ++ } ++ ++ smem->partitions[remote_host] = header; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_count_mem_regions(struct platform_device *pdev) ++{ ++ struct resource *res; ++ int num_regions = 0; ++ int i; ++ ++ for (i = 0; i < pdev->num_resources; i++) { ++ res = &pdev->resource[i]; ++ ++ if (resource_type(res) == IORESOURCE_MEM) ++ num_regions++; ++ } ++ ++ return num_regions; ++} ++ ++static int qcom_smem_probe(struct platform_device *pdev) ++{ ++ struct smem_header *header; ++ struct device_node *np; ++ struct qcom_smem *smem; ++ struct resource *res; ++ struct resource r; ++ size_t array_size; ++ int num_regions = 0; ++ int hwlock_id; ++ u32 version; ++ int ret; ++ int i; ++ ++ num_regions = qcom_smem_count_mem_regions(pdev) + 1; ++ ++ array_size = num_regions * sizeof(struct smem_region); ++ smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); ++ if (!smem) ++ return -ENOMEM; ++ ++ smem->dev = &pdev->dev; ++ smem->num_regions = num_regions; ++ ++ np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); ++ if (!np) { ++ dev_err(&pdev->dev, "No memory-region specified\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_address_to_resource(np, 0, &r); ++ of_node_put(np); ++ if (ret) ++ return ret; ++ ++ smem->regions[0].aux_base = (u32)r.start; ++ smem->regions[0].size = resource_size(&r); ++ smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev, ++ r.start, ++ resource_size(&r)); ++ if (!smem->regions[0].virt_base) ++ return -ENOMEM; ++ ++ for (i = 1; i < num_regions; i++) { ++ res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1); ++ ++ smem->regions[i].aux_base = (u32)res->start; ++ smem->regions[i].size = resource_size(res); ++ smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev, ++ res->start, ++ resource_size(res)); ++ if (!smem->regions[i].virt_base) ++ return -ENOMEM; ++ } ++ ++ header = smem->regions[0].virt_base; ++ if (header->initialized != 1 || header->reserved) { ++ dev_err(&pdev->dev, "SMEM is not initilized by SBL\n"); ++ return -EINVAL; ++ } ++ ++ version = qcom_smem_get_sbl_version(smem); ++ if (version >> 16 != SMEM_EXPECTED_VERSION) { ++ dev_err(&pdev->dev, "Unsupported smem version 0x%x\n", version); ++ return -EINVAL; ++ } ++ ++ ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); ++ if (ret < 0) ++ return ret; ++ ++ hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); ++ if (hwlock_id < 0) { ++ dev_err(&pdev->dev, "failed to retrieve hwlock\n"); ++ return hwlock_id; ++ } ++ ++ smem->hwlock = hwspin_lock_request_specific(hwlock_id); ++ if (!smem->hwlock) ++ return -ENXIO; ++ ++ __smem = smem; ++ ++ return 0; ++} ++ ++static int qcom_smem_remove(struct platform_device *pdev) ++{ ++ hwspin_lock_free(__smem->hwlock); ++ __smem = NULL; ++ ++ return 0; ++} ++ ++static const struct of_device_id qcom_smem_of_match[] = { ++ { .compatible = "qcom,smem" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, qcom_smem_of_match); ++ ++static struct platform_driver qcom_smem_driver = { ++ .probe = qcom_smem_probe, ++ .remove = qcom_smem_remove, ++ .driver = { ++ .name = "qcom_smem", ++ .of_match_table = qcom_smem_of_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++static int __init qcom_smem_init(void) ++{ ++ return platform_driver_register(&qcom_smem_driver); ++} ++arch_initcall(qcom_smem_init); ++ ++static void __exit qcom_smem_exit(void) ++{ ++ platform_driver_unregister(&qcom_smem_driver); ++} ++module_exit(qcom_smem_exit) ++ ++MODULE_AUTHOR("Bjorn Andersson "); ++MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); ++MODULE_LICENSE("GPLv2"); +--- /dev/null ++++ b/include/linux/soc/qcom/smem.h +@@ -0,0 +1,14 @@ ++#ifndef __QCOM_SMEM_H__ ++#define __QCOM_SMEM_H__ ++ ++struct device_node; ++struct qcom_smem; ++ ++#define QCOM_SMEM_HOST_ANY -1 ++ ++int qcom_smem_alloc(unsigned host, unsigned item, size_t size); ++int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size); ++ ++int qcom_smem_get_free_space(unsigned host); ++ ++#endif diff --git a/target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch new file mode 100644 index 0000000..b2b9a74 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch @@ -0,0 +1,36 @@ +From f212be3a6134db8dd7c5f6f0987536a669401fae Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 14 Aug 2015 11:17:20 -0700 +Subject: [PATCH 2/3] ARM: qcom: add SMEM device node to IPQ806x dts + +SMEM is used on IPQ806x to store various board related information such +as boot device and flash partition layout. We'll declare it as a device +so we can make use of it thanks to the new SMEM soc driver. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -54,7 +54,7 @@ + no-map; + }; + +- smem@41000000 { ++ smem: smem@41000000 { + reg = <0x41000000 0x200000>; + no-map; + }; +@@ -304,4 +304,10 @@ + + #hwlock-cells = <1>; + }; ++ ++ smem { ++ compatible = "qcom,smem"; ++ memory-region = <&smem>; ++ hwlocks = <&sfpb_mutex 3>; ++ }; + }; diff --git a/target/linux/ipq806x/patches-3.18/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch b/target/linux/ipq806x/patches-3.18/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch new file mode 100644 index 0000000..b2c8cd5 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch @@ -0,0 +1,277 @@ +From 0501f76b138cf1dc11a313bb7a094da524b79337 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 13 Aug 2015 09:53:14 -0700 +Subject: [PATCH 3/3] mtd: add SMEM parser for QCOM platforms + +On QCOM platforms using MTD devices storage (such as IPQ806x), SMEM is +used to store partition layout. This new parser can now be used to read +SMEM and use it to register an MTD layout according to its content. + +Signed-off-by: Mathieu Olivari +--- + drivers/mtd/Kconfig | 7 ++ + drivers/mtd/Makefile | 1 + + drivers/mtd/qcom_smem_part.c | 231 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 239 insertions(+) + create mode 100644 drivers/mtd/qcom_smem_part.c + +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -200,6 +200,13 @@ config MTD_MYLOADER_PARTS + You will still need the parsing functions to be called by the driver + for your particular device. It won't happen automatically. + ++config MTD_QCOM_SMEM_PARTS ++ tristate "QCOM SMEM partitioning support" ++ depends on QCOM_SMEM ++ help ++ This provides partitions parser for QCOM devices using SMEM ++ such as IPQ806x. ++ + comment "User Modules And Translation Layers" + + # +--- /dev/null ++++ b/drivers/mtd/qcom_smem_part.c +@@ -0,0 +1,231 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++/* Processor/host identifier for the application processor */ ++#define SMEM_HOST_APPS 0 ++ ++/* SMEM items index */ ++#define SMEM_AARM_PARTITION_TABLE 9 ++#define SMEM_BOOT_FLASH_TYPE 421 ++#define SMEM_BOOT_FLASH_BLOCK_SIZE 424 ++ ++/* SMEM Flash types */ ++#define SMEM_FLASH_NAND 2 ++#define SMEM_FLASH_SPI 6 ++ ++#define SMEM_PART_NAME_SZ 16 ++#define SMEM_PARTS_MAX 32 ++ ++struct smem_partition { ++ char name[SMEM_PART_NAME_SZ]; ++ __le32 start; ++ __le32 size; ++ __le32 attr; ++}; ++ ++struct smem_partition_table { ++ u8 magic[8]; ++ __le32 version; ++ __le32 len; ++ struct smem_partition parts[SMEM_PARTS_MAX]; ++}; ++ ++/* SMEM Magic values in partition table */ ++static const u8 SMEM_PTABLE_MAGIC[] = { ++ 0xaa, 0x73, 0xee, 0x55, ++ 0xdb, 0xbd, 0x5e, 0xe3, ++}; ++ ++static int qcom_smem_get_flash_blksz(u64 **smem_blksz) ++{ ++ int ret; ++ size_t size; ++ ++ ret = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_BLOCK_SIZE, ++ (void **) smem_blksz, &size); ++ ++ if (ret < 0) { ++ pr_err("Unable to read flash blksz from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ if (size != sizeof(**smem_blksz)) { ++ pr_err("Invalid flash blksz size in SMEM\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_get_flash_type(u64 **smem_flash_type) ++{ ++ int ret; ++ size_t size; ++ ++ ret = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_TYPE, ++ (void **) smem_flash_type, &size); ++ ++ if (ret < 0) { ++ pr_err("Unable to read flash type from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ if (size != sizeof(**smem_flash_type)) { ++ pr_err("Invalid flash type size in SMEM\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_get_flash_partitions(struct smem_partition_table **pparts) ++{ ++ int ret; ++ size_t size; ++ ++ ret = qcom_smem_get(SMEM_HOST_APPS, SMEM_AARM_PARTITION_TABLE, ++ (void **) pparts, &size); ++ ++ if (ret < 0) { ++ pr_err("Unable to read partition table from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ return 0; ++} ++ ++static int of_dev_node_match(struct device *dev, void *data) ++{ ++ return dev->of_node == data; ++} ++ ++static bool is_spi_device(struct device_node *np) ++{ ++ struct device *dev; ++ ++ dev = bus_find_device(&spi_bus_type, NULL, np, of_dev_node_match); ++ if (!dev) ++ return false; ++ ++ put_device(dev); ++ return true; ++} ++ ++static int parse_qcom_smem_partitions(struct mtd_info *master, ++ struct mtd_partition **pparts, ++ struct mtd_part_parser_data *data) ++{ ++ struct smem_partition_table *smem_parts; ++ u64 *smem_flash_type, *smem_blksz; ++ struct mtd_partition *mtd_parts; ++ struct device_node *of_node = data->of_node; ++ int i, ret; ++ ++ /* ++ * SMEM will only store the partition table of the boot device. ++ * If this is not the boot device, do not return any partition. ++ */ ++ ret = qcom_smem_get_flash_type(&smem_flash_type); ++ if (ret < 0) ++ return ret; ++ ++ if ((*smem_flash_type == SMEM_FLASH_NAND && !mtd_type_is_nand(master)) ++ || (*smem_flash_type == SMEM_FLASH_SPI && !is_spi_device(of_node))) ++ return 0; ++ ++ /* ++ * Just for sanity purpose, make sure the block size in SMEM matches the ++ * block size of the MTD device ++ */ ++ ret = qcom_smem_get_flash_blksz(&smem_blksz); ++ if (ret < 0) ++ return ret; ++ ++ if (*smem_blksz != master->erasesize) { ++ pr_err("SMEM block size differs from MTD block size\n"); ++ return -EINVAL; ++ } ++ ++ /* Get partition pointer from SMEM */ ++ ret = qcom_smem_get_flash_partitions(&smem_parts); ++ if (ret < 0) ++ return ret; ++ ++ if (memcmp(SMEM_PTABLE_MAGIC, smem_parts->magic, ++ sizeof(SMEM_PTABLE_MAGIC))) { ++ pr_err("SMEM partition magic invalid\n"); ++ return -EINVAL; ++ } ++ ++ /* Allocate and populate the mtd structures */ ++ mtd_parts = kcalloc(le32_to_cpu(smem_parts->len), sizeof(*mtd_parts), ++ GFP_KERNEL); ++ if (!mtd_parts) ++ return -ENOMEM; ++ ++ for (i = 0; i < smem_parts->len; i++) { ++ struct smem_partition *s_part = &smem_parts->parts[i]; ++ struct mtd_partition *m_part = &mtd_parts[i]; ++ ++ m_part->name = s_part->name; ++ m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz); ++ m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz); ++ ++ /* ++ * The last SMEM partition may have its size marked as ++ * something like 0xffffffff, which means "until the end of the ++ * flash device". In this case, truncate it. ++ */ ++ if (m_part->offset + m_part->size > master->size) ++ m_part->size = master->size - m_part->offset; ++ } ++ ++ *pparts = mtd_parts; ++ ++ return smem_parts->len; ++} ++ ++static struct mtd_part_parser qcom_smem_parser = { ++ .owner = THIS_MODULE, ++ .parse_fn = parse_qcom_smem_partitions, ++ .name = "qcom-smem", ++}; ++ ++static int __init qcom_smem_parser_init(void) ++{ ++ register_mtd_parser(&qcom_smem_parser); ++ return 0; ++} ++ ++static void __exit qcom_smem_parser_exit(void) ++{ ++ deregister_mtd_parser(&qcom_smem_parser); ++} ++ ++module_init(qcom_smem_parser_init); ++module_exit(qcom_smem_parser_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Mathieu Olivari "); ++MODULE_DESCRIPTION("Parsing code for SMEM based partition tables"); +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -16,6 +16,7 @@ obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o + obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o + obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o + obj-$(CONFIG_MTD_MYLOADER_PARTS) += myloader.o ++obj-$(CONFIG_MTD_QCOM_SMEM_PARTS) += qcom_smem_part.o + + # 'Users' - code which presents functionality to userspace. + obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o diff --git a/target/linux/ipq806x/patches-3.18/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch b/target/linux/ipq806x/patches-3.18/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch new file mode 100644 index 0000000..25803b8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch @@ -0,0 +1,511 @@ +--- a/drivers/phy/Kconfig ++++ b/drivers/phy/Kconfig +@@ -256,4 +256,15 @@ config PHY_STIH41X_USB + Enable this to support the USB transceiver that is part of + STMicroelectronics STiH41x SoC series. + ++config PHY_QCOM_DWC3 ++ tristate "QCOM DWC3 USB PHY support" ++ depends on ARCH_QCOM ++ depends on HAS_IOMEM ++ depends on OF ++ select GENERIC_PHY ++ help ++ This option enables support for the Synopsis PHYs present inside the ++ Qualcomm USB3.0 DWC3 controller. This driver supports both HS and SS ++ PHY controllers. ++ + endmenu +--- a/drivers/phy/Makefile ++++ b/drivers/phy/Makefile +@@ -31,3 +31,4 @@ obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += + obj-$(CONFIG_PHY_XGENE) += phy-xgene.o + obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o + obj-$(CONFIG_PHY_STIH41X_USB) += phy-stih41x-usb.o ++obj-$(CONFIG_PHY_QCOM_DWC3) += phy-qcom-dwc3.o +--- /dev/null ++++ b/drivers/phy/phy-qcom-dwc3.c +@@ -0,0 +1,483 @@ ++/* Copyright (c) 2013-2014, Code Aurora Forum. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/** ++ * USB QSCRATCH Hardware registers ++ */ ++#define QSCRATCH_GENERAL_CFG (0x08) ++#define HSUSB_PHY_CTRL_REG (0x10) ++ ++/* PHY_CTRL_REG */ ++#define HSUSB_CTRL_DMSEHV_CLAMP BIT(24) ++#define HSUSB_CTRL_USB2_SUSPEND BIT(23) ++#define HSUSB_CTRL_UTMI_CLK_EN BIT(21) ++#define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20) ++#define HSUSB_CTRL_USE_CLKCORE BIT(18) ++#define HSUSB_CTRL_DPSEHV_CLAMP BIT(17) ++#define HSUSB_CTRL_COMMONONN BIT(11) ++#define HSUSB_CTRL_ID_HV_CLAMP BIT(9) ++#define HSUSB_CTRL_OTGSESSVLD_CLAMP BIT(8) ++#define HSUSB_CTRL_CLAMP_EN BIT(7) ++#define HSUSB_CTRL_RETENABLEN BIT(1) ++#define HSUSB_CTRL_POR BIT(0) ++ ++/* QSCRATCH_GENERAL_CFG */ ++#define HSUSB_GCFG_XHCI_REV BIT(2) ++ ++/** ++ * USB QSCRATCH Hardware registers ++ */ ++#define SSUSB_PHY_CTRL_REG (0x00) ++#define SSUSB_PHY_PARAM_CTRL_1 (0x04) ++#define SSUSB_PHY_PARAM_CTRL_2 (0x08) ++#define CR_PROTOCOL_DATA_IN_REG (0x0c) ++#define CR_PROTOCOL_DATA_OUT_REG (0x10) ++#define CR_PROTOCOL_CAP_ADDR_REG (0x14) ++#define CR_PROTOCOL_CAP_DATA_REG (0x18) ++#define CR_PROTOCOL_READ_REG (0x1c) ++#define CR_PROTOCOL_WRITE_REG (0x20) ++ ++/* PHY_CTRL_REG */ ++#define SSUSB_CTRL_REF_USE_PAD BIT(28) ++#define SSUSB_CTRL_TEST_POWERDOWN BIT(27) ++#define SSUSB_CTRL_LANE0_PWR_PRESENT BIT(24) ++#define SSUSB_CTRL_SS_PHY_EN BIT(8) ++#define SSUSB_CTRL_SS_PHY_RESET BIT(7) ++ ++/* SSPHY control registers */ ++#define SSPHY_CTRL_RX_OVRD_IN_HI(lane) (0x1006 + 0x100 * lane) ++#define SSPHY_CTRL_TX_OVRD_DRV_LO(lane) (0x1002 + 0x100 * lane) ++ ++/* RX OVRD IN HI bits */ ++#define RX_OVRD_IN_HI_RX_RESET_OVRD BIT(13) ++#define RX_OVRD_IN_HI_RX_RX_RESET BIT(12) ++#define RX_OVRD_IN_HI_RX_EQ_OVRD BIT(11) ++#define RX_OVRD_IN_HI_RX_EQ_MASK 0x0700 ++#define RX_OVRD_IN_HI_RX_EQ_SHIFT 8 ++#define RX_OVRD_IN_HI_RX_EQ_EN_OVRD BIT(7) ++#define RX_OVRD_IN_HI_RX_EQ_EN BIT(6) ++#define RX_OVRD_IN_HI_RX_LOS_FILTER_OVRD BIT(5) ++#define RX_OVRD_IN_HI_RX_LOS_FILTER_MASK 0x0018 ++#define RX_OVRD_IN_HI_RX_RATE_OVRD BIT(2) ++#define RX_OVRD_IN_HI_RX_RATE_MASK 0x0003 ++ ++/* TX OVRD DRV LO register bits */ ++#define TX_OVRD_DRV_LO_AMPLITUDE_MASK 0x007F ++#define TX_OVRD_DRV_LO_PREEMPH_MASK 0x3F80 ++#define TX_OVRD_DRV_LO_PREEMPH_SHIFT 7 ++#define TX_OVRD_DRV_LO_EN BIT(14) ++ ++struct qcom_dwc3_usb_phy { ++ void __iomem *base; ++ struct device *dev; ++ struct phy *phy; ++ ++ int (*phy_init)(struct qcom_dwc3_usb_phy *phy_dwc3); ++ int (*phy_exit)(struct qcom_dwc3_usb_phy *phy_dwc3); ++ ++ struct clk *xo_clk; ++ struct clk *ref_clk; ++}; ++ ++/** ++ * Write register and read back masked value to confirm it is written ++ * ++ * @base - QCOM DWC3 PHY base virtual address. ++ * @offset - register offset. ++ * @mask - register bitmask specifying what should be updated ++ * @val - value to write. ++ */ ++static inline void qcom_dwc3_phy_write_readback( ++ struct qcom_dwc3_usb_phy *phy_dwc3, u32 offset, ++ const u32 mask, u32 val) ++{ ++ u32 write_val, tmp = readl(phy_dwc3->base + offset); ++ ++ tmp &= ~mask; /* retain other bits */ ++ write_val = tmp | val; ++ ++ writel(write_val, phy_dwc3->base + offset); ++ ++ /* Read back to see if val was written */ ++ tmp = readl(phy_dwc3->base + offset); ++ tmp &= mask; /* clear other bits */ ++ ++ if (tmp != val) ++ dev_err(phy_dwc3->dev, "write: %x to QSCRATCH: %x FAILED\n", ++ val, offset); ++} ++ ++static int wait_for_latch(void __iomem *addr) ++{ ++ u32 retry = 10; ++ ++ while (true) { ++ if (!readl(addr)) ++ break; ++ ++ if (--retry == 0) ++ return -ETIMEDOUT; ++ ++ usleep_range(10, 20); ++ } ++ ++ return 0; ++} ++ ++/** ++ * Write SSPHY register ++ * ++ * @base - QCOM DWC3 PHY base virtual address. ++ * @addr - SSPHY address to write. ++ * @val - value to write. ++ */ ++static int qcom_dwc3_ss_write_phycreg(void __iomem *base, u32 addr, u32 val) ++{ ++ int ret; ++ ++ writel(addr, base + CR_PROTOCOL_DATA_IN_REG); ++ writel(0x1, base + CR_PROTOCOL_CAP_ADDR_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG); ++ if (ret) ++ goto err_wait; ++ ++ writel(val, base + CR_PROTOCOL_DATA_IN_REG); ++ writel(0x1, base + CR_PROTOCOL_CAP_DATA_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_CAP_DATA_REG); ++ if (ret) ++ goto err_wait; ++ ++ writel(0x1, base + CR_PROTOCOL_WRITE_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_WRITE_REG); ++ ++err_wait: ++ return ret; ++} ++ ++/** ++ * Read SSPHY register. ++ * ++ * @base - QCOM DWC3 PHY base virtual address. ++ * @addr - SSPHY address to read. ++ */ ++static int qcom_dwc3_ss_read_phycreg(void __iomem *base, u32 addr, u32 *val) ++{ ++ int ret; ++ bool first_read = true; ++ ++ writel(addr, base + CR_PROTOCOL_DATA_IN_REG); ++ writel(0x1, base + CR_PROTOCOL_CAP_ADDR_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG); ++ if (ret) ++ goto err_wait; ++ ++ /* ++ * Due to hardware bug, first read of SSPHY register might be ++ * incorrect. Hence as workaround, SW should perform SSPHY register ++ * read twice, but use only second read and ignore first read. ++ */ ++retry: ++ writel(0x1, base + CR_PROTOCOL_READ_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_READ_REG); ++ if (ret) ++ goto err_wait; ++ ++ if (first_read) { ++ readl(base + CR_PROTOCOL_DATA_OUT_REG); ++ first_read = false; ++ goto retry; ++ } ++ ++ *val = readl(base + CR_PROTOCOL_DATA_OUT_REG); ++ ++err_wait: ++ return ret; ++} ++ ++static int qcom_dwc3_phy_power_on(struct phy *phy) ++{ ++ int ret; ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ ret = clk_prepare_enable(phy_dwc3->xo_clk); ++ if (ret) ++ return ret; ++ ++ ret = clk_prepare_enable(phy_dwc3->ref_clk); ++ if (ret) ++ clk_disable_unprepare(phy_dwc3->xo_clk); ++ ++ return ret; ++} ++ ++static int qcom_dwc3_phy_power_off(struct phy *phy) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ clk_disable_unprepare(phy_dwc3->ref_clk); ++ clk_disable_unprepare(phy_dwc3->xo_clk); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_hs_phy_init(struct qcom_dwc3_usb_phy *phy_dwc3) ++{ ++ u32 val; ++ ++ /* ++ * HSPHY Initialization: Enable UTMI clock, select 19.2MHz fsel ++ * enable clamping, and disable RETENTION (power-on default is ENABLED) ++ */ ++ val = HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_DMSEHV_CLAMP | ++ HSUSB_CTRL_RETENABLEN | HSUSB_CTRL_COMMONONN | ++ HSUSB_CTRL_OTGSESSVLD_CLAMP | HSUSB_CTRL_ID_HV_CLAMP | ++ HSUSB_CTRL_DPSEHV_CLAMP | HSUSB_CTRL_UTMI_OTG_VBUS_VALID | ++ HSUSB_CTRL_UTMI_CLK_EN | HSUSB_CTRL_CLAMP_EN | 0x70; ++ ++ /* use core clock if external reference is not present */ ++ if (!phy_dwc3->xo_clk) ++ val |= HSUSB_CTRL_USE_CLKCORE; ++ ++ writel(val, phy_dwc3->base + HSUSB_PHY_CTRL_REG); ++ usleep_range(2000, 2200); ++ ++ /* Disable (bypass) VBUS and ID filters */ ++ writel(HSUSB_GCFG_XHCI_REV, phy_dwc3->base + QSCRATCH_GENERAL_CFG); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_ss_phy_init(struct qcom_dwc3_usb_phy *phy_dwc3) ++{ ++ int ret; ++ u32 data = 0; ++ ++ /* reset phy */ ++ data = readl_relaxed(phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ writel_relaxed(data | SSUSB_CTRL_SS_PHY_RESET, ++ phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ usleep_range(2000, 2200); ++ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ ++ /* clear REF_PAD if we don't have XO clk */ ++ if (!phy_dwc3->xo_clk) ++ data &= ~SSUSB_CTRL_REF_USE_PAD; ++ else ++ data |= SSUSB_CTRL_REF_USE_PAD; ++ ++ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ msleep(30); ++ ++ data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT; ++ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ ++ /* ++ * Fix RX Equalization setting as follows ++ * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 ++ * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 ++ * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3 ++ * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 ++ */ ++ ret = qcom_dwc3_ss_read_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_RX_OVRD_IN_HI(0), &data); ++ if (ret) ++ goto err_phy_trans; ++ ++ data &= ~RX_OVRD_IN_HI_RX_EQ_EN; ++ data |= RX_OVRD_IN_HI_RX_EQ_EN_OVRD; ++ data &= ~RX_OVRD_IN_HI_RX_EQ_MASK; ++ data |= 0x3 << RX_OVRD_IN_HI_RX_EQ_SHIFT; ++ data |= RX_OVRD_IN_HI_RX_EQ_OVRD; ++ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_RX_OVRD_IN_HI(0), data); ++ if (ret) ++ goto err_phy_trans; ++ ++ /* ++ * Set EQ and TX launch amplitudes as follows ++ * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22 ++ * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127 ++ * LANE0.TX_OVRD_DRV_LO.EN set to 1. ++ */ ++ ret = qcom_dwc3_ss_read_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_TX_OVRD_DRV_LO(0), &data); ++ if (ret) ++ goto err_phy_trans; ++ ++ data &= ~TX_OVRD_DRV_LO_PREEMPH_MASK; ++ data |= 0x16 << TX_OVRD_DRV_LO_PREEMPH_SHIFT; ++ data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK; ++ data |= 0x7f; ++ data |= TX_OVRD_DRV_LO_EN; ++ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3->base, ++ SSPHY_CTRL_TX_OVRD_DRV_LO(0), data); ++ if (ret) ++ goto err_phy_trans; ++ ++ /* ++ * Set the QSCRATCH PHY_PARAM_CTRL1 parameters as follows ++ * TX_FULL_SWING [26:20] amplitude to 127 ++ * TX_DEEMPH_3_5DB [13:8] to 22 ++ * LOS_BIAS [2:0] to 0x5 ++ */ ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_PARAM_CTRL_1, ++ 0x07f03f07, 0x07f01605); ++ ++err_phy_trans: ++ return ret; ++} ++ ++static int qcom_dwc3_ss_phy_exit(struct qcom_dwc3_usb_phy *phy_dwc3) ++{ ++ /* Sequence to put SSPHY in low power state: ++ * 1. Clear REF_PHY_EN in PHY_CTRL_REG ++ * 2. Clear REF_USE_PAD in PHY_CTRL_REG ++ * 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention ++ * 4. Disable SSPHY ref clk ++ */ ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, ++ SSUSB_CTRL_SS_PHY_EN, 0x0); ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, ++ SSUSB_CTRL_REF_USE_PAD, 0x0); ++ qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, ++ 0x0, SSUSB_CTRL_TEST_POWERDOWN); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_phy_init(struct phy *phy) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ if (phy_dwc3->phy_init) ++ return phy_dwc3->phy_init(phy_dwc3); ++ ++ return 0; ++} ++ ++static int qcom_dwc3_phy_exit(struct phy *phy) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ ++ if (phy_dwc3->phy_exit) ++ return qcom_dwc3_ss_phy_exit(phy_dwc3); ++ ++ return 0; ++} ++ ++static struct phy_ops qcom_dwc3_phy_ops = { ++ .init = qcom_dwc3_phy_init, ++ .exit = qcom_dwc3_phy_exit, ++ .power_on = qcom_dwc3_phy_power_on, ++ .power_off = qcom_dwc3_phy_power_off, ++ .owner = THIS_MODULE, ++}; ++ ++static const struct of_device_id qcom_dwc3_phy_table[] = { ++ { .compatible = "qcom,dwc3-hs-usb-phy", }, ++ { .compatible = "qcom,dwc3-ss-usb-phy", }, ++ { /* Sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, qcom_dwc3_phy_table); ++ ++static int qcom_dwc3_phy_probe(struct platform_device *pdev) ++{ ++ struct qcom_dwc3_usb_phy *phy_dwc3; ++ struct phy_provider *phy_provider; ++ struct resource *res; ++ ++ phy_dwc3 = devm_kzalloc(&pdev->dev, sizeof(*phy_dwc3), GFP_KERNEL); ++ if (!phy_dwc3) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, phy_dwc3); ++ ++ phy_dwc3->dev = &pdev->dev; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ phy_dwc3->base = devm_ioremap_resource(phy_dwc3->dev, res); ++ if (IS_ERR(phy_dwc3->base)) ++ return PTR_ERR(phy_dwc3->base); ++ ++ phy_dwc3->ref_clk = devm_clk_get(phy_dwc3->dev, "ref"); ++ if (IS_ERR(phy_dwc3->ref_clk)) { ++ dev_dbg(phy_dwc3->dev, "cannot get reference clock\n"); ++ return PTR_ERR(phy_dwc3->ref_clk); ++ } ++ ++ if (of_device_is_compatible(pdev->dev.of_node, ++ "qcom,dwc3-hs-usb-phy")) { ++ clk_set_rate(phy_dwc3->ref_clk, 60000000); ++ phy_dwc3->phy_init = qcom_dwc3_hs_phy_init; ++ } else if (of_device_is_compatible(pdev->dev.of_node, ++ "qcom,dwc3-ss-usb-phy")) { ++ phy_dwc3->phy_init = qcom_dwc3_ss_phy_init; ++ phy_dwc3->phy_exit = qcom_dwc3_ss_phy_exit; ++ clk_set_rate(phy_dwc3->ref_clk, 125000000); ++ } else { ++ dev_err(phy_dwc3->dev, "Unknown phy\n"); ++ return -EINVAL; ++ } ++ ++ phy_dwc3->xo_clk = devm_clk_get(phy_dwc3->dev, "xo"); ++ if (IS_ERR(phy_dwc3->xo_clk)) { ++ dev_dbg(phy_dwc3->dev, "cannot get TCXO clock\n"); ++ phy_dwc3->xo_clk = NULL; ++ } ++ ++ phy_dwc3->phy = devm_phy_create(phy_dwc3->dev, NULL, &qcom_dwc3_phy_ops, ++ NULL); ++ ++ if (IS_ERR(phy_dwc3->phy)) ++ return PTR_ERR(phy_dwc3->phy); ++ ++ phy_set_drvdata(phy_dwc3->phy, phy_dwc3); ++ ++ phy_provider = devm_of_phy_provider_register(phy_dwc3->dev, ++ of_phy_simple_xlate); ++ ++ if (IS_ERR(phy_provider)) ++ return PTR_ERR(phy_provider); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_dwc3_phy_driver = { ++ .probe = qcom_dwc3_phy_probe, ++ .driver = { ++ .name = "qcom-dwc3-usb-phy", ++ .owner = THIS_MODULE, ++ .of_match_table = qcom_dwc3_phy_table, ++ }, ++}; ++ ++module_platform_driver(qcom_dwc3_phy_driver); ++ ++MODULE_ALIAS("platform:phy-qcom-dwc3"); ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Andy Gross "); ++MODULE_AUTHOR("Ivan T. Ivanov "); ++MODULE_DESCRIPTION("DesignWare USB3 QCOM PHY driver"); diff --git a/target/linux/ipq806x/patches-3.18/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch b/target/linux/ipq806x/patches-3.18/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch new file mode 100644 index 0000000..e2d03d4 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/101-ARM-qcom-add-USB-nodes-to-ipq806x-ap148.patch @@ -0,0 +1,126 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -91,5 +91,29 @@ + sata@29000000 { + status = "ok"; + }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -296,6 +296,91 @@ + compatible = "syscon"; + reg = <0x01200600 0x100>; + }; ++ ++ hs_phy_1: phy@100f8800 { ++ compatible = "qcom,dwc3-hs-usb-phy"; ++ reg = <0x100f8800 0x30>; ++ clocks = <&gcc USB30_1_UTMI_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ ss_phy_1: phy@100f8830 { ++ compatible = "qcom,dwc3-ss-usb-phy"; ++ reg = <0x100f8830 0x30>; ++ clocks = <&gcc USB30_1_MASTER_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ hs_phy_0: phy@110f8800 { ++ compatible = "qcom,dwc3-hs-usb-phy"; ++ reg = <0x110f8800 0x30>; ++ clocks = <&gcc USB30_0_UTMI_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ ss_phy_0: phy@110f8830 { ++ compatible = "qcom,dwc3-ss-usb-phy"; ++ reg = <0x110f8830 0x30>; ++ clocks = <&gcc USB30_0_MASTER_CLK>; ++ clock-names = "ref"; ++ #phy-cells = <0>; ++ ++ status = "disabled"; ++ }; ++ ++ usb3_0: usb30@0 { ++ compatible = "qcom,dwc3"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gcc USB30_0_MASTER_CLK>; ++ clock-names = "core"; ++ ++ ranges; ++ ++ status = "disabled"; ++ ++ dwc3@11000000 { ++ compatible = "snps,dwc3"; ++ reg = <0x11000000 0xcd00>; ++ interrupts = <0 110 0x4>; ++ phys = <&hs_phy_0>, <&ss_phy_0>; ++ phy-names = "usb2-phy", "usb3-phy"; ++ tx-fifo-resize; ++ dr_mode = "host"; ++ }; ++ }; ++ ++ usb3_1: usb30@1 { ++ compatible = "qcom,dwc3"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ clocks = <&gcc USB30_1_MASTER_CLK>; ++ clock-names = "core"; ++ ++ ranges; ++ ++ status = "disabled"; ++ ++ dwc3@10000000 { ++ compatible = "snps,dwc3"; ++ reg = <0x10000000 0xcd00>; ++ interrupts = <0 205 0x4>; ++ phys = <&hs_phy_1>, <&ss_phy_1>; ++ phy-names = "usb2-phy", "usb3-phy"; ++ tx-fifo-resize; ++ dr_mode = "host"; ++ }; ++ }; ++ + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-3.18/102-soc-qcom-gsbi-Add-support-for-ADM-CRCI-muxing.patch b/target/linux/ipq806x/patches-3.18/102-soc-qcom-gsbi-Add-support-for-ADM-CRCI-muxing.patch new file mode 100644 index 0000000..752f3f7 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/102-soc-qcom-gsbi-Add-support-for-ADM-CRCI-muxing.patch @@ -0,0 +1,249 @@ +--- a/Documentation/devicetree/bindings/soc/qcom/qcom,gsbi.txt ++++ b/Documentation/devicetree/bindings/soc/qcom/qcom,gsbi.txt +@@ -6,7 +6,8 @@ configuration settings. The mode settin + the 4 GSBI IOs. + + Required properties: +-- compatible: must contain "qcom,gsbi-v1.0.0" for APQ8064/IPQ8064 ++- compatible: Should contain "qcom,gsbi-v1.0.0" ++- cell-index: Should contain the GSBI index + - reg: Address range for GSBI registers + - clocks: required clock + - clock-names: must contain "iface" entry +@@ -16,6 +17,8 @@ Required properties: + Optional properties: + - qcom,crci : indicates CRCI MUX value for QUP CRCI ports. Please reference + dt-bindings/soc/qcom,gsbi.h for valid CRCI mux values. ++- syscon-tcsr: indicates phandle of TCSR syscon node. Required if child uses ++ dma. + + Required properties if child node exists: + - #address-cells: Must be 1 +@@ -39,6 +42,7 @@ Example for APQ8064: + + gsbi4@16300000 { + compatible = "qcom,gsbi-v1.0.0"; ++ cell-index = <4>; + reg = <0x16300000 0x100>; + clocks = <&gcc GSBI4_H_CLK>; + clock-names = "iface"; +@@ -48,6 +52,8 @@ Example for APQ8064: + qcom,mode = ; + qcom,crci = ; + ++ syscon-tcsr = <&tcsr>; ++ + /* child nodes go under here */ + + i2c_qup4: i2c@16380000 { +@@ -76,3 +82,9 @@ Example for APQ8064: + }; + }; + ++ tcsr: syscon@1a400000 { ++ compatible = "qcom,apq8064-tcsr", "syscon"; ++ reg = <0x1a400000 0x100>; ++ }; ++ ++ +--- a/drivers/soc/qcom/Kconfig ++++ b/drivers/soc/qcom/Kconfig +@@ -4,6 +4,7 @@ + config QCOM_GSBI + tristate "QCOM General Serial Bus Interface" + depends on ARCH_QCOM ++ select MFD_SYSCON + help + Say y here to enable GSBI support. The GSBI provides control + functions for connecting the underlying serial UART, SPI, and I2C +--- a/drivers/soc/qcom/qcom_gsbi.c ++++ b/drivers/soc/qcom/qcom_gsbi.c +@@ -18,22 +18,129 @@ + #include + #include + #include ++#include ++#include ++#include + + #define GSBI_CTRL_REG 0x0000 + #define GSBI_PROTOCOL_SHIFT 4 ++#define MAX_GSBI 12 ++ ++#define TCSR_ADM_CRCI_BASE 0x70 ++ ++struct crci_config { ++ u32 num_rows; ++ const u32 (*array)[MAX_GSBI]; ++}; ++ ++static const u32 crci_ipq8064[][MAX_GSBI] = { ++ { ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000c00, 0x003000, 0x00c000, ++ 0x030000, 0x0c0000, 0x300000, 0xc00000 ++ }, ++ { ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000c00, 0x003000, 0x00c000, ++ 0x030000, 0x0c0000, 0x300000, 0xc00000 ++ }, ++}; ++ ++static const struct crci_config config_ipq8064 = { ++ .num_rows = ARRAY_SIZE(crci_ipq8064), ++ .array = crci_ipq8064, ++}; ++ ++static const unsigned int crci_apq8064[][MAX_GSBI] = { ++ { ++ 0x001800, 0x006000, 0x000030, 0x0000c0, ++ 0x000300, 0x000400, 0x000000, 0x000000, ++ 0x000000, 0x000000, 0x000000, 0x000000 ++ }, ++ { ++ 0x000000, 0x000000, 0x000000, 0x000000, ++ 0x000000, 0x000020, 0x0000c0, 0x000000, ++ 0x000000, 0x000000, 0x000000, 0x000000 ++ }, ++}; ++ ++static const struct crci_config config_apq8064 = { ++ .num_rows = ARRAY_SIZE(crci_apq8064), ++ .array = crci_apq8064, ++}; ++ ++static const unsigned int crci_msm8960[][MAX_GSBI] = { ++ { ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000400, 0x000000, 0x000000, ++ 0x000000, 0x000000, 0x000000, 0x000000 ++ }, ++ { ++ 0x000000, 0x000000, 0x000000, 0x000000, ++ 0x000000, 0x000020, 0x0000c0, 0x000300, ++ 0x001800, 0x006000, 0x000000, 0x000000 ++ }, ++}; ++ ++static const struct crci_config config_msm8960 = { ++ .num_rows = ARRAY_SIZE(crci_msm8960), ++ .array = crci_msm8960, ++}; ++ ++static const unsigned int crci_msm8660[][MAX_GSBI] = { ++ { /* ADM 0 - B */ ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000c00, 0x003000, 0x00c000, ++ 0x030000, 0x0c0000, 0x300000, 0xc00000 ++ }, ++ { /* ADM 0 - B */ ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000c00, 0x003000, 0x00c000, ++ 0x030000, 0x0c0000, 0x300000, 0xc00000 ++ }, ++ { /* ADM 1 - A */ ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000c00, 0x003000, 0x00c000, ++ 0x030000, 0x0c0000, 0x300000, 0xc00000 ++ }, ++ { /* ADM 1 - B */ ++ 0x000003, 0x00000c, 0x000030, 0x0000c0, ++ 0x000300, 0x000c00, 0x003000, 0x00c000, ++ 0x030000, 0x0c0000, 0x300000, 0xc00000 ++ }, ++}; ++ ++static const struct crci_config config_msm8660 = { ++ .num_rows = ARRAY_SIZE(crci_msm8660), ++ .array = crci_msm8660, ++}; + + struct gsbi_info { + struct clk *hclk; + u32 mode; + u32 crci; ++ struct regmap *tcsr; ++}; ++ ++static const struct of_device_id tcsr_dt_match[] = { ++ { .compatible = "qcom,tcsr-ipq8064", .data = &config_ipq8064}, ++ { .compatible = "qcom,tcsr-apq8064", .data = &config_apq8064}, ++ { .compatible = "qcom,tcsr-msm8960", .data = &config_msm8960}, ++ { .compatible = "qcom,tcsr-msm8660", .data = &config_msm8660}, ++ { }, + }; + + static int gsbi_probe(struct platform_device *pdev) + { + struct device_node *node = pdev->dev.of_node; ++ struct device_node *tcsr_node; ++ const struct of_device_id *match; + struct resource *res; + void __iomem *base; + struct gsbi_info *gsbi; ++ int i; ++ u32 mask, gsbi_num; ++ const struct crci_config *config = NULL; + + gsbi = devm_kzalloc(&pdev->dev, sizeof(*gsbi), GFP_KERNEL); + +@@ -45,6 +152,32 @@ static int gsbi_probe(struct platform_de + if (IS_ERR(base)) + return PTR_ERR(base); + ++ /* get the tcsr node and setup the config and regmap */ ++ gsbi->tcsr = syscon_regmap_lookup_by_phandle(node, "syscon-tcsr"); ++ ++ if (!IS_ERR(gsbi->tcsr)) { ++ tcsr_node = of_parse_phandle(node, "syscon-tcsr", 0); ++ if (tcsr_node) { ++ match = of_match_node(tcsr_dt_match, tcsr_node); ++ if (match) ++ config = match->data; ++ else ++ dev_warn(&pdev->dev, "no matching TCSR\n"); ++ ++ of_node_put(tcsr_node); ++ } ++ } ++ ++ if (of_property_read_u32(node, "cell-index", &gsbi_num)) { ++ dev_err(&pdev->dev, "missing cell-index\n"); ++ return -EINVAL; ++ } ++ ++ if (gsbi_num < 1 || gsbi_num > MAX_GSBI) { ++ dev_err(&pdev->dev, "invalid cell-index\n"); ++ return -EINVAL; ++ } ++ + if (of_property_read_u32(node, "qcom,mode", &gsbi->mode)) { + dev_err(&pdev->dev, "missing mode configuration\n"); + return -EINVAL; +@@ -64,6 +197,25 @@ static int gsbi_probe(struct platform_de + writel_relaxed((gsbi->mode << GSBI_PROTOCOL_SHIFT) | gsbi->crci, + base + GSBI_CTRL_REG); + ++ /* ++ * modify tcsr to reflect mode and ADM CRCI mux ++ * Each gsbi contains a pair of bits, one for RX and one for TX ++ * SPI mode requires both bits cleared, otherwise they are set ++ */ ++ if (config) { ++ for (i = 0; i < config->num_rows; i++) { ++ mask = config->array[i][gsbi_num - 1]; ++ ++ if (gsbi->mode == GSBI_PROT_SPI) ++ regmap_update_bits(gsbi->tcsr, ++ TCSR_ADM_CRCI_BASE + 4 * i, mask, 0); ++ else ++ regmap_update_bits(gsbi->tcsr, ++ TCSR_ADM_CRCI_BASE + 4 * i, mask, mask); ++ ++ } ++ } ++ + /* make sure the gsbi control write is not reordered */ + wmb(); + diff --git a/target/linux/ipq806x/patches-3.18/103-ARM-DT-ipq8064-Add-TCSR-support.patch b/target/linux/ipq806x/patches-3.18/103-ARM-DT-ipq8064-Add-TCSR-support.patch new file mode 100644 index 0000000..322e1d9 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/103-ARM-DT-ipq8064-Add-TCSR-support.patch @@ -0,0 +1,65 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -132,6 +132,7 @@ + + gsbi2: gsbi@12480000 { + compatible = "qcom,gsbi-v1.0.0"; ++ cell-index = <2>; + reg = <0x12480000 0x100>; + clocks = <&gcc GSBI2_H_CLK>; + clock-names = "iface"; +@@ -140,6 +141,8 @@ + ranges; + status = "disabled"; + ++ syscon-tcsr = <&tcsr>; ++ + uart2: serial@12490000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x12490000 0x1000>, +@@ -167,6 +170,7 @@ + + gsbi4: gsbi@16300000 { + compatible = "qcom,gsbi-v1.0.0"; ++ cell-index = <4>; + reg = <0x16300000 0x100>; + clocks = <&gcc GSBI4_H_CLK>; + clock-names = "iface"; +@@ -175,6 +179,8 @@ + ranges; + status = "disabled"; + ++ syscon-tcsr = <&tcsr>; ++ + uart4: serial@16340000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x16340000 0x1000>, +@@ -201,6 +207,7 @@ + + gsbi5: gsbi@1a200000 { + compatible = "qcom,gsbi-v1.0.0"; ++ cell-index = <5>; + reg = <0x1a200000 0x100>; + clocks = <&gcc GSBI5_H_CLK>; + clock-names = "iface"; +@@ -209,6 +216,8 @@ + ranges; + status = "disabled"; + ++ syscon-tcsr = <&tcsr>; ++ + uart5: serial@1a240000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x1a240000 0x1000>, +@@ -279,6 +288,11 @@ + status = "disabled"; + }; + ++ tcsr: syscon@1a400000 { ++ compatible = "qcom,tcsr-ipq8064", "syscon"; ++ reg = <0x1a400000 0x100>; ++ }; ++ + qcom,ssbi@500000 { + compatible = "qcom,ssbi"; + reg = <0x00500000 0x1000>; diff --git a/target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch b/target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch new file mode 100644 index 0000000..41f91fa --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch @@ -0,0 +1,263 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,3/5] DT: PCI: qcom: Document PCIe devicetree bindings +From: Stanimir Varbanov +X-Patchwork-Id: 6326181 +Message-Id: <1430743338-10441-4-git-send-email-svarbanov@mm-sol.com> +To: Rob Herring , Kumar Gala , + Mark Rutland , + Grant Likely , + Bjorn Helgaas , + Kishon Vijay Abraham I , + Russell King , Arnd Bergmann +Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, + linux-pci@vger.kernel.org, Mathieu Olivari , + Srinivas Kandagatla , + Stanimir Varbanov +Date: Mon, 4 May 2015 15:42:16 +0300 + +Document Qualcomm PCIe driver devicetree bindings. + +Signed-off-by: Stanimir Varbanov + +--- +.../devicetree/bindings/pci/qcom,pcie.txt | 231 ++++++++++++++++++++ + 1 files changed, 231 insertions(+), 0 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt +@@ -0,0 +1,231 @@ ++* Qualcomm PCI express root complex ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: Value shall include ++ - "qcom,pcie-v0" for apq/ipq8064 ++ - "qcom,pcie-v1" for apq8084 ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: Register ranges as listed in the reg-names property ++ ++- reg-names: ++ Usage: required ++ Value type: ++ Definition: Must include the following entries ++ - "parf" Qualcomm specific registers ++ - "dbi" Designware PCIe registers ++ - "elbi" External local bus interface registers ++ - "config" PCIe configuration space ++ ++- device_type: ++ Usage: required ++ Value type: ++ Definition: Should be "pci". As specified in designware-pcie.txt ++ ++- #address-cells: ++ Usage: required ++ Value type: ++ Definition: Should be set to 3. As specified in designware-pcie.txt ++ ++- #size-cells: ++ Usage: required ++ Value type: ++ Definition: Should be set 2. As specified in designware-pcie.txt ++ ++- ranges: ++ Usage: required ++ Value type: ++ Definition: As specified in designware-pcie.txt ++ ++- interrupts: ++ Usage: required ++ Value type: ++ Definition: MSI interrupt ++ ++- interrupt-names: ++ Usage: required ++ Value type: ++ Definition: Should contain "msi" ++ ++- #interrupt-cells: ++ Usage: required ++ Value type: ++ Definition: Should be 1. As specified in designware-pcie.txt ++ ++- interrupt-map-mask: ++ Usage: required ++ Value type: ++ Definition: As specified in designware-pcie.txt ++ ++- interrupt-map: ++ Usage: required ++ Value type: ++ Definition: As specified in designware-pcie.txt ++ ++- clocks: ++ Usage: required ++ Value type: ++ Definition: List of phandle and clock specifier pairs as listed ++ in clock-names property ++ ++- clock-names: ++ Usage: required ++ Value type: ++ Definition: Should contain the following entries ++ * should be populated for v0 and v1 ++ - "iface" Configuration AHB clock ++ ++ * should be populated for v0 ++ - "core" Clocks the pcie hw block ++ - "phy" Clocks the pcie PHY block ++ ++ * should be populated for v1 ++ - "aux" Auxiliary (AUX) clock ++ - "bus_master" Master AXI clock ++ - "bus_slave" Slave AXI clock ++ ++- resets: ++ Usage: required ++ Value type: ++ Definition: List of phandle and reset specifier pairs as listed ++ in reset-names property ++ ++- reset-names: ++ Usage: required ++ Value type: ++ Definition: Should contain the following entries ++ * should be populated for v0 ++ - "axi" AXI reset ++ - "ahb" AHB reset ++ - "por" POR reset ++ - "pci" PCI reset ++ - "phy" PHY reset ++ ++ * should be populated for v1 ++ - "core" Core reset ++ ++- power-domains: ++ Usage: required (for v1 only) ++ Value type: ++ Definition: A phandle and power domain specifier pair to the ++ power domain which is responsible for collapsing ++ and restoring power to the peripheral ++ ++- -supply: ++ Usage: required ++ Value type: ++ Definition: List of phandles to the power supply regulator(s) ++ * should be populated for v0 and v1 ++ - "vdda" core analog power supply ++ ++ * should be populated for v0 ++ - "vdda_phy" analog power supply for PHY ++ - "vdda_refclk" analog power supply for IC which generate ++ reference clock ++ ++- phys: ++ Usage: required (for v1 only) ++ Value type: ++ Definition: List of phandle(s) as listed in phy-names property ++ ++- phy-names: ++ Usage: required (for v1 only) ++ Value type: ++ Definition: Should contain "pciephy" ++ ++- -gpio: ++ Usage: optional ++ Value type: ++ Definition: List of phandle and gpio specifier pairs. Should contain ++ - "perst" PCIe endpoint reset signal line ++ - "pewake" PCIe endpoint wake signal line ++ ++- pinctrl-0: ++ Usage: required ++ Value type: ++ Definition: List of phandles pointing at a pin(s) configuration ++ ++- pinctrl-names ++ Usage: required ++ Value type: ++ Definition: List of names of pinctrl-0 state ++ ++* Example for v0 ++ pcie0: pci@1b500000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b500000 0x1000 ++ 0x1b502000 0x80 ++ 0x1b600000 0x100 ++ 0x0ff00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */ ++ 0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* memory */ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ clocks = <&gcc PCIE_A_CLK>, ++ <&gcc PCIE_H_CLK>, ++ <&gcc PCIE_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ resets = <&gcc PCIE_ACLK_RESET>, ++ <&gcc PCIE_HCLK_RESET>, ++ <&gcc PCIE_POR_RESET>, ++ <&gcc PCIE_PCI_RESET>, ++ <&gcc PCIE_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ }; ++ ++* Example for v1 ++ pcie0@fc520000 { ++ compatible = "qcom,pcie-v1"; ++ reg = <0xfc520000 0x2000>, ++ <0xff000000 0x1000>, ++ <0xff001000 0x1000>, ++ <0xff002000 0x2000>; ++ reg-names = "parf", "dbi", "elbi", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges = <0x81000000 0 0 0xff200000 0 0x00100000 /* I/O */ ++ 0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>, ++ <&gcc GCC_PCIE_0_MSTR_AXI_CLK>, ++ <&gcc GCC_PCIE_0_SLV_AXI_CLK>, ++ <&gcc GCC_PCIE_0_AUX_CLK>; ++ clock-names = "iface", "master_bus", "slave_bus", "aux"; ++ resets = <&gcc GCC_PCIE_0_BCR>; ++ reset-names = "core"; ++ power-domains = <&gcc PCIE0_GDSC>; ++ vdda-supply = <&pma8084_l3>; ++ phys = <&pciephy0>; ++ phy-names = "pciephy"; ++ perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>; ++ pinctrl-0 = <&pcie0_pins_default>; ++ pinctrl-names = "default"; ++ }; diff --git a/target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch b/target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch new file mode 100644 index 0000000..a0c9c7c --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch @@ -0,0 +1,753 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,4/5] PCI: qcom: Add Qualcomm PCIe controller driver +From: Stanimir Varbanov +X-Patchwork-Id: 6326161 +Message-Id: <1430743338-10441-5-git-send-email-svarbanov@mm-sol.com> +To: Rob Herring , Kumar Gala , + Mark Rutland , + Grant Likely , + Bjorn Helgaas , + Kishon Vijay Abraham I , + Russell King , Arnd Bergmann +Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, + linux-pci@vger.kernel.org, Mathieu Olivari , + Srinivas Kandagatla , + Stanimir Varbanov +Date: Mon, 4 May 2015 15:42:17 +0300 + +The PCIe driver reuse the Designware common code for host +and MSI initialization, and also program the Qualcomm +application specific registers. + +Signed-off-by: Stanimir Varbanov + +--- +MAINTAINERS | 7 + + drivers/pci/host/Kconfig | 9 + + drivers/pci/host/Makefile | 1 + + drivers/pci/host/pcie-qcom.c | 677 ++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 694 insertions(+), 0 deletions(-) + create mode 100644 drivers/pci/host/pcie-qcom.c + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -7127,6 +7127,13 @@ L: linux-pci@vger.kernel.org + S: Maintained + F: drivers/pci/host/*spear* + ++PCIE DRIVER FOR QUALCOMM MSM ++M: Stanimir Varbanov ++L: linux-pci@vger.kernel.org ++L: linux-arm-msm@vger.kernel.org ++S: Maintained ++F: drivers/pci/host/*qcom* ++ + PCMCIA SUBSYSTEM + P: Linux PCMCIA Team + L: linux-pcmcia@lists.infradead.org +--- a/drivers/pci/host/Kconfig ++++ b/drivers/pci/host/Kconfig +@@ -91,4 +91,13 @@ config PCI_XGENE + There are 5 internal PCIe ports available. Each port is GEN3 capable + and have varied lanes from x1 to x8. + ++config PCIE_QCOM ++ bool "Qualcomm PCIe controller" ++ depends on ARCH_QCOM && OF || (ARM && COMPILE_TEST) ++ select PCIE_DW ++ select PCIEPORTBUS ++ help ++ Say Y here to enable PCIe controller support on Qualcomm SoCs. The ++ PCIe controller use Designware core plus Qualcomm specific hardware ++ wrappers. + endmenu +--- /dev/null ++++ b/drivers/pci/host/pcie-qcom.c +@@ -0,0 +1,677 @@ ++/* ++ * Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pcie-designware.h" ++ ++#define PCIE20_PARF_PHY_CTRL 0x40 ++#define PCIE20_PARF_PHY_REFCLK 0x4C ++#define PCIE20_PARF_DBI_BASE_ADDR 0x168 ++#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c ++#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 ++ ++#define PCIE20_ELBI_SYS_CTRL 0x04 ++#define PCIE20_ELBI_SYS_STTS 0x08 ++#define XMLH_LINK_UP BIT(10) ++ ++#define PCIE20_CAP 0x70 ++#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) ++ ++#define PERST_DELAY_MIN_US 1000 ++#define PERST_DELAY_MAX_US 1005 ++ ++#define LINKUP_DELAY_MIN_US 5000 ++#define LINKUP_DELAY_MAX_US 5100 ++#define LINKUP_RETRIES_COUNT 20 ++ ++#define PCIE_V0 0 /* apq8064 */ ++#define PCIE_V1 1 /* apq8084 */ ++ ++struct qcom_pcie_resources_v0 { ++ struct clk *iface_clk; ++ struct clk *core_clk; ++ struct clk *phy_clk; ++ struct reset_control *pci_reset; ++ struct reset_control *axi_reset; ++ struct reset_control *ahb_reset; ++ struct reset_control *por_reset; ++ struct reset_control *phy_reset; ++ struct regulator *vdda; ++ struct regulator *vdda_phy; ++ struct regulator *vdda_refclk; ++}; ++ ++struct qcom_pcie_resources_v1 { ++ struct clk *iface; ++ struct clk *aux; ++ struct clk *master_bus; ++ struct clk *slave_bus; ++ struct reset_control *core; ++ struct regulator *vdda; ++}; ++ ++union pcie_resources { ++ struct qcom_pcie_resources_v0 v0; ++ struct qcom_pcie_resources_v1 v1; ++}; ++ ++struct qcom_pcie { ++ struct pcie_port pp; ++ struct device *dev; ++ union pcie_resources res; ++ void __iomem *parf; ++ void __iomem *dbi; ++ void __iomem *elbi; ++ struct phy *phy; ++ struct gpio_desc *reset; ++ unsigned int version; ++}; ++ ++#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp) ++ ++static inline void ++writel_masked(void __iomem *addr, u32 clear_mask, u32 set_mask) ++{ ++ u32 val = readl(addr); ++ ++ val &= ~clear_mask; ++ val |= set_mask; ++ writel(val, addr); ++} ++ ++static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert) ++{ ++ int val, active_low; ++ ++ if (IS_ERR_OR_NULL(pcie->reset)) ++ return; ++ ++ active_low = gpiod_is_active_low(pcie->reset); ++ ++ if (assert) ++ val = !!active_low; ++ else ++ val = !active_low; ++ ++ gpiod_set_value(pcie->reset, val); ++ ++ usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US); ++} ++ ++static void qcom_ep_reset_assert(struct qcom_pcie *pcie) ++{ ++ qcom_ep_reset_assert_deassert(pcie, 1); ++} ++ ++static void qcom_ep_reset_deassert(struct qcom_pcie *pcie) ++{ ++ qcom_ep_reset_assert_deassert(pcie, 0); ++} ++ ++static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg) ++{ ++ struct pcie_port *pp = arg; ++ ++ return dw_handle_msi_irq(pp); ++} ++ ++static int qcom_pcie_link_up(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS); ++ ++ return val & BIT(29) ? 1 : 0; ++} ++ ++static void qcom_pcie_disable_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ ++ reset_control_assert(res->pci_reset); ++ reset_control_assert(res->axi_reset); ++ reset_control_assert(res->ahb_reset); ++ reset_control_assert(res->por_reset); ++ reset_control_assert(res->pci_reset); ++ clk_disable_unprepare(res->iface_clk); ++ clk_disable_unprepare(res->core_clk); ++ clk_disable_unprepare(res->phy_clk); ++ regulator_disable(res->vdda); ++ regulator_disable(res->vdda_phy); ++ regulator_disable(res->vdda_refclk); ++} ++ ++static void qcom_pcie_disable_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ ++ reset_control_assert(res->core); ++ clk_disable_unprepare(res->slave_bus); ++ clk_disable_unprepare(res->master_bus); ++ clk_disable_unprepare(res->iface); ++ clk_disable_unprepare(res->aux); ++ regulator_disable(res->vdda); ++} ++ ++static int qcom_pcie_enable_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ ret = regulator_enable(res->vdda); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda regulator\n"); ++ return ret; ++ } ++ ++ ret = regulator_enable(res->vdda_refclk); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda_refclk regulator\n"); ++ goto err_refclk; ++ } ++ ++ ret = regulator_enable(res->vdda_phy); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda_phy regulator\n"); ++ goto err_vdda_phy; ++ } ++ ++ ret = clk_prepare_enable(res->iface_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable iface clock\n"); ++ goto err_iface; ++ } ++ ++ ret = clk_prepare_enable(res->core_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable core clock\n"); ++ goto err_clk_core; ++ } ++ ++ ret = clk_prepare_enable(res->phy_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable phy clock\n"); ++ goto err_clk_phy; ++ } ++ ++ ret = reset_control_deassert(res->ahb_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert ahb reset\n"); ++ goto err_reset_ahb; ++ } ++ ++ return 0; ++ ++err_reset_ahb: ++ clk_disable_unprepare(res->phy_clk); ++err_clk_phy: ++ clk_disable_unprepare(res->core_clk); ++err_clk_core: ++ clk_disable_unprepare(res->iface_clk); ++err_iface: ++ regulator_disable(res->vdda_phy); ++err_vdda_phy: ++ regulator_disable(res->vdda_refclk); ++err_refclk: ++ regulator_disable(res->vdda); ++ return ret; ++} ++ ++static int qcom_pcie_enable_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ ret = reset_control_deassert(res->core); ++ if (ret) { ++ dev_err(dev, "cannot deassert core reset\n"); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(res->aux); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable aux clock\n"); ++ goto err_res; ++ } ++ ++ ret = clk_prepare_enable(res->iface); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable iface clock\n"); ++ goto err_aux; ++ } ++ ++ ret = clk_prepare_enable(res->master_bus); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable master_bus clock\n"); ++ goto err_iface; ++ } ++ ++ ret = clk_prepare_enable(res->slave_bus); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable slave_bus clock\n"); ++ goto err_master; ++ } ++ ++ ret = regulator_enable(res->vdda); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda regulator\n"); ++ goto err_slave; ++ } ++ ++ return 0; ++ ++err_slave: ++ clk_disable_unprepare(res->slave_bus); ++err_master: ++ clk_disable_unprepare(res->master_bus); ++err_iface: ++ clk_disable_unprepare(res->iface); ++err_aux: ++ clk_disable_unprepare(res->aux); ++err_res: ++ reset_control_assert(res->core); ++ ++ return ret; ++} ++ ++static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ ++ res->vdda = devm_regulator_get(dev, "vdda"); ++ if (IS_ERR(res->vdda)) ++ return PTR_ERR(res->vdda); ++ ++ res->vdda_phy = devm_regulator_get(dev, "vdda_phy"); ++ if (IS_ERR(res->vdda_phy)) ++ return PTR_ERR(res->vdda_phy); ++ ++ res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk"); ++ if (IS_ERR(res->vdda_refclk)) ++ return PTR_ERR(res->vdda_refclk); ++ ++ res->iface_clk = devm_clk_get(dev, "iface"); ++ if (IS_ERR(res->iface_clk)) ++ return PTR_ERR(res->iface_clk); ++ ++ res->core_clk = devm_clk_get(dev, "core"); ++ if (IS_ERR(res->core_clk)) ++ return PTR_ERR(res->core_clk); ++ ++ res->phy_clk = devm_clk_get(dev, "phy"); ++ if (IS_ERR(res->phy_clk)) ++ return PTR_ERR(res->phy_clk); ++ ++ res->pci_reset = devm_reset_control_get(dev, "pci"); ++ if (IS_ERR(res->pci_reset)) ++ return PTR_ERR(res->pci_reset); ++ ++ res->axi_reset = devm_reset_control_get(dev, "axi"); ++ if (IS_ERR(res->axi_reset)) ++ return PTR_ERR(res->axi_reset); ++ ++ res->ahb_reset = devm_reset_control_get(dev, "ahb"); ++ if (IS_ERR(res->ahb_reset)) ++ return PTR_ERR(res->ahb_reset); ++ ++ res->por_reset = devm_reset_control_get(dev, "por"); ++ if (IS_ERR(res->por_reset)) ++ return PTR_ERR(res->por_reset); ++ ++ res->phy_reset = devm_reset_control_get(dev, "phy"); ++ if (IS_ERR(res->phy_reset)) ++ return PTR_ERR(res->phy_reset); ++ ++ return 0; ++} ++ ++static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ struct device *dev = pcie->dev; ++ ++ res->vdda = devm_regulator_get(dev, "vdda"); ++ if (IS_ERR(res->vdda)) ++ return PTR_ERR(res->vdda); ++ ++ res->iface = devm_clk_get(dev, "iface"); ++ if (IS_ERR(res->iface)) ++ return PTR_ERR(res->iface); ++ ++ res->aux = devm_clk_get(dev, "aux"); ++ if (IS_ERR(res->aux) && PTR_ERR(res->aux) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ else if (IS_ERR(res->aux)) ++ res->aux = NULL; ++ ++ res->master_bus = devm_clk_get(dev, "master_bus"); ++ if (IS_ERR(res->master_bus)) ++ return PTR_ERR(res->master_bus); ++ ++ res->slave_bus = devm_clk_get(dev, "slave_bus"); ++ if (IS_ERR(res->slave_bus)) ++ return PTR_ERR(res->slave_bus); ++ ++ res->core = devm_reset_control_get(dev, "core"); ++ if (IS_ERR(res->core)) ++ return PTR_ERR(res->core); ++ ++ return 0; ++} ++ ++static int qcom_pcie_enable_link_training(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ struct device *dev = pp->dev; ++ int retries; ++ u32 val; ++ ++ /* enable link training */ ++ writel_masked(pcie->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0)); ++ ++ /* wait for up to 100ms for the link to come up */ ++ retries = LINKUP_RETRIES_COUNT; ++ do { ++ val = readl(pcie->elbi + PCIE20_ELBI_SYS_STTS); ++ if (val & XMLH_LINK_UP) ++ break; ++ usleep_range(LINKUP_DELAY_MIN_US, LINKUP_DELAY_MAX_US); ++ } while (retries--); ++ ++ if (retries < 0 || !dw_pcie_link_up(pp)) { ++ dev_err(dev, "link initialization failed\n"); ++ return -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static void qcom_pcie_host_init_v1(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ int ret; ++ ++ qcom_ep_reset_assert(pcie); ++ ++ ret = qcom_pcie_enable_resources_v1(pcie); ++ if (ret) ++ return; ++ ++ /* change DBI base address */ ++ writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ writel_masked(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT, ++ 0, BIT(31)); ++ ++ ret = phy_init(pcie->phy); ++ if (ret) ++ goto err_res; ++ ++ ret = phy_power_on(pcie->phy); ++ if (ret) ++ goto err_phy; ++ ++ dw_pcie_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ dw_pcie_msi_init(pp); ++ ++ qcom_ep_reset_deassert(pcie); ++ ++ ret = qcom_pcie_enable_link_training(pp); ++ if (ret) ++ goto err; ++ ++ return; ++ ++err: ++ qcom_ep_reset_assert(pcie); ++ phy_power_off(pcie->phy); ++err_phy: ++ phy_exit(pcie->phy); ++err_res: ++ qcom_pcie_disable_resources_v1(pcie); ++} ++ ++static void qcom_pcie_host_init_v0(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ qcom_ep_reset_assert(pcie); ++ ++ ret = qcom_pcie_enable_resources_v0(pcie); ++ if (ret) ++ return; ++ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); ++ ++ /* enable external reference clock */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); ++ ++ ret = reset_control_deassert(res->phy_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert phy reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->pci_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert pci reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->por_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert por reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->axi_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert axi reset\n"); ++ return; ++ } ++ ++ /* wait 150ms for clock acquisition */ ++ usleep_range(10000, 15000); ++ ++ dw_pcie_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ dw_pcie_msi_init(pp); ++ ++ qcom_ep_reset_deassert(pcie); ++ ++ ret = qcom_pcie_enable_link_training(pp); ++ if (ret) ++ goto err; ++ ++ return; ++err: ++ qcom_ep_reset_assert(pcie); ++ qcom_pcie_disable_resources_v0(pcie); ++} ++ ++static void qcom_pcie_host_init(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ ++ if (pcie->version == PCIE_V0) ++ return qcom_pcie_host_init_v0(pp); ++ else ++ return qcom_pcie_host_init_v1(pp); ++} ++ ++static int ++qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val) ++{ ++ /* the device class is not reported correctly from the register */ ++ if (where == PCI_CLASS_REVISION && size == 4) { ++ *val = readl(pp->dbi_base + PCI_CLASS_REVISION); ++ *val &= ~(0xffff << 16); ++ *val |= PCI_CLASS_BRIDGE_PCI << 16; ++ return PCIBIOS_SUCCESSFUL; ++ } ++ ++ return dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where, ++ size, val); ++} ++ ++static struct pcie_host_ops qcom_pcie_ops = { ++ .link_up = qcom_pcie_link_up, ++ .host_init = qcom_pcie_host_init, ++ .rd_own_conf = qcom_pcie_rd_own_conf, ++}; ++ ++static const struct of_device_id qcom_pcie_match[] = { ++ { .compatible = "qcom,pcie-v0", .data = (void *)PCIE_V0 }, ++ { .compatible = "qcom,pcie-v1", .data = (void *)PCIE_V1 }, ++ { } ++}; ++ ++static int qcom_pcie_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_device_id *match; ++ struct resource *res; ++ struct qcom_pcie *pcie; ++ struct pcie_port *pp; ++ int ret; ++ ++ match = of_match_node(qcom_pcie_match, dev->of_node); ++ if (!match) ++ return -ENXIO; ++ ++ pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); ++ if (!pcie) ++ return -ENOMEM; ++ ++ pcie->version = (unsigned int)match->data; ++ ++ pcie->reset = devm_gpiod_get_optional(dev, "perst"); ++ if (IS_ERR(pcie->reset) && PTR_ERR(pcie->reset) == -EPROBE_DEFER) ++ return PTR_ERR(pcie->reset); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf"); ++ pcie->parf = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->parf)) ++ return PTR_ERR(pcie->parf); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); ++ pcie->dbi = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->dbi)) ++ return PTR_ERR(pcie->dbi); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); ++ pcie->elbi = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->elbi)) ++ return PTR_ERR(pcie->elbi); ++ ++ pcie->phy = devm_phy_optional_get(dev, "pciephy"); ++ if (IS_ERR(pcie->phy)) ++ return PTR_ERR(pcie->phy); ++ ++ pcie->dev = dev; ++ ++ if (pcie->version == PCIE_V0) ++ ret = qcom_pcie_get_resources_v0(pcie); ++ else ++ ret = qcom_pcie_get_resources_v1(pcie); ++ ++ if (ret) ++ return ret; ++ ++ pp = &pcie->pp; ++ pp->dev = dev; ++ pp->dbi_base = pcie->dbi; ++ pp->root_bus_nr = -1; ++ pp->ops = &qcom_pcie_ops; ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) { ++ pp->msi_irq = platform_get_irq_byname(pdev, "msi"); ++ if (pp->msi_irq < 0) { ++ dev_err(dev, "cannot get msi irq\n"); ++ return pp->msi_irq; ++ } ++ ++ ret = devm_request_irq(dev, pp->msi_irq, ++ qcom_pcie_msi_irq_handler, ++ IRQF_SHARED, "qcom-pcie-msi", pp); ++ if (ret) { ++ dev_err(dev, "cannot request msi irq\n"); ++ return ret; ++ } ++ } ++ ++ ret = dw_pcie_host_init(pp); ++ if (ret) { ++ dev_err(dev, "cannot initialize host\n"); ++ return ret; ++ } ++ ++ platform_set_drvdata(pdev, pcie); ++ ++ return 0; ++} ++ ++static int qcom_pcie_remove(struct platform_device *pdev) ++{ ++ struct qcom_pcie *pcie = platform_get_drvdata(pdev); ++ ++ qcom_ep_reset_assert(pcie); ++ phy_power_off(pcie->phy); ++ phy_exit(pcie->phy); ++ if (pcie->version == PCIE_V0) ++ qcom_pcie_disable_resources_v0(pcie); ++ else ++ qcom_pcie_disable_resources_v1(pcie); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_pcie_driver = { ++ .probe = qcom_pcie_probe, ++ .remove = qcom_pcie_remove, ++ .driver = { ++ .name = "qcom-pcie", ++ .of_match_table = qcom_pcie_match, ++ }, ++}; ++ ++module_platform_driver(qcom_pcie_driver); ++ ++MODULE_AUTHOR("Stanimir Varbanov "); ++MODULE_DESCRIPTION("Qualcomm PCIe root complex driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:qcom-pcie"); +--- a/drivers/pci/host/Makefile ++++ b/drivers/pci/host/Makefile +@@ -11,3 +11,4 @@ obj-$(CONFIG_PCIE_SPEAR13XX) += pcie-spe + obj-$(CONFIG_PCI_KEYSTONE) += pci-keystone-dw.o pci-keystone.o + obj-$(CONFIG_PCIE_XILINX) += pcie-xilinx.o + obj-$(CONFIG_PCI_XGENE) += pci-xgene.o ++obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o diff --git a/target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch new file mode 100644 index 0000000..d15f0ac --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch @@ -0,0 +1,266 @@ +From 5b40516b2f5fb9b2a7d6d3e2e924f12ec9d183a8 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Tue, 21 Apr 2015 19:01:42 -0700 +Subject: [PATCH 8/9] ARM: dts: qcom: add pcie nodes to ipq806x platforms + +qcom-pcie driver now supports version 0 of the controller. This change +adds the corresponding entries to the IPQ806x dtsi file and +corresponding platform (AP148). + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 30 ++++++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 124 +++++++++++++++++++++++++++++++ + 2 files changed, 154 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -35,6 +35,24 @@ + bias-disable; + }; + ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ + spi_pins: spi_pins { + mux { + pins = "gpio18", "gpio19", "gpio21"; +@@ -115,5 +133,19 @@ + usb30@1 { + status = "ok"; + }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -30,6 +30,33 @@ + bias-disable; + }; + ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie2_pins: pcie2_pinmux { ++ mux { ++ pins = "gpio63"; ++ function = "pcie3_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ + spi_pins: spi_pins { + mux { + pins = "gpio18", "gpio19", "gpio21"; +@@ -128,5 +155,26 @@ + usb30@1 { + status = "ok"; + }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie2: pci@1b900000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 63 0>; ++ pinctrl-0 = <&pcie2_pins>; ++ pinctrl-names = "default"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -3,6 +3,8 @@ + #include "skeleton.dtsi" + #include + #include ++#include ++#include + + / { + model = "Qualcomm IPQ8064"; +@@ -311,6 +313,129 @@ + reg = <0x01200600 0x100>; + }; + ++ pcie0: pci@1b500000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b500000 0x1000 ++ 0x1b502000 0x80 ++ 0x1b600000 0x100 ++ 0x0ff00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* non-prefetchable memory */ ++ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_A_CLK>, ++ <&gcc PCIE_H_CLK>, ++ <&gcc PCIE_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_ACLK_RESET>, ++ <&gcc PCIE_HCLK_RESET>, ++ <&gcc PCIE_POR_RESET>, ++ <&gcc PCIE_PCI_RESET>, ++ <&gcc PCIE_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ status = "disabled"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b700000 0x1000 ++ 0x1b702000 0x80 ++ 0x1b800000 0x100 ++ 0x31f00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <1>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0 0x31e00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x00000000 0x2e000000 0 0x03e00000>; /* non-prefetchable memory */ ++ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 58 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 59 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 60 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 61 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_1_A_CLK>, ++ <&gcc PCIE_1_H_CLK>, ++ <&gcc PCIE_1_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_1_ACLK_RESET>, ++ <&gcc PCIE_1_HCLK_RESET>, ++ <&gcc PCIE_1_POR_RESET>, ++ <&gcc PCIE_1_PCI_RESET>, ++ <&gcc PCIE_1_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ status = "disabled"; ++ }; ++ ++ pcie2: pci@1b900000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b900000 0x1000 ++ 0x1b902000 0x80 ++ 0x1ba00000 0x100 ++ 0x35f00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <2>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0 0x35e00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x00000000 0x32000000 0 0x03e00000>; /* non-prefetchable memory */ ++ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 72 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 73 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 74 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_2_A_CLK>, ++ <&gcc PCIE_2_H_CLK>, ++ <&gcc PCIE_2_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_2_ACLK_RESET>, ++ <&gcc PCIE_2_HCLK_RESET>, ++ <&gcc PCIE_2_POR_RESET>, ++ <&gcc PCIE_2_PCI_RESET>, ++ <&gcc PCIE_2_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ status = "disabled"; ++ }; ++ + hs_phy_1: phy@100f8800 { + compatible = "qcom,dwc3-hs-usb-phy"; + reg = <0x100f8800 0x30>; diff --git a/target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch b/target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch new file mode 100644 index 0000000..4f0e45f --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch @@ -0,0 +1,29 @@ +From f004aa1dec6e2e206be025de15b115d60f2b21e3 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Tue, 21 Apr 2015 19:09:07 -0700 +Subject: [PATCH 9/9] ARM: qcom: automatically select PCI_DOMAINS if PCI is + enabled + +If multiple PCIe devices are present in the system, the kernel will +panic at boot time when trying to scan the PCI buses. This happens on +IPQ806x based platforms, which has 3 PCIe ports. + +Enabling this option allows the kernel to assign the pci-domains +according to the device-tree content. This allows multiple PCIe +controllers to coexist in the system. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/mach-qcom/Kconfig | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/arm/mach-qcom/Kconfig ++++ b/arch/arm/mach-qcom/Kconfig +@@ -6,6 +6,7 @@ menuconfig ARCH_QCOM + select CLKSRC_OF + select PINCTRL + select QCOM_SCM if SMP ++ select PCI_DOMAINS if PCI + help + Support for Qualcomm's devicetree based systems. + diff --git a/target/linux/ipq806x/patches-3.18/114-pcie-add-ctlr-init.patch b/target/linux/ipq806x/patches-3.18/114-pcie-add-ctlr-init.patch new file mode 100644 index 0000000..11c9810 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/114-pcie-add-ctlr-init.patch @@ -0,0 +1,278 @@ +--- a/drivers/pci/host/pcie-qcom.c ++++ b/drivers/pci/host/pcie-qcom.c +@@ -29,8 +29,53 @@ + + #include "pcie-designware.h" + ++/* DBI registers */ ++#define PCIE20_CAP 0x70 ++#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) ++ ++#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818 ++#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c ++ ++#define PCIE20_PLR_IATU_VIEWPORT 0x900 ++#define PCIE20_PLR_IATU_REGION_OUTBOUND (0x0 << 31) ++#define PCIE20_PLR_IATU_REGION_INDEX(x) (x << 0) ++ ++#define PCIE20_PLR_IATU_CTRL1 0x904 ++#define PCIE20_PLR_IATU_TYPE_CFG0 (0x4 << 0) ++#define PCIE20_PLR_IATU_TYPE_MEM (0x0 << 0) ++ ++#define PCIE20_PLR_IATU_CTRL2 0x908 ++#define PCIE20_PLR_IATU_ENABLE BIT(31) ++ ++#define PCIE20_PLR_IATU_LBAR 0x90C ++#define PCIE20_PLR_IATU_UBAR 0x910 ++#define PCIE20_PLR_IATU_LAR 0x914 ++#define PCIE20_PLR_IATU_LTAR 0x918 ++#define PCIE20_PLR_IATU_UTAR 0x91c ++ ++#define MSM_PCIE_DEV_CFG_ADDR 0x01000000 ++ ++/* PARF registers */ ++#define PCIE20_PARF_PCS_DEEMPH 0x34 ++#define PCS_DEEMPH_TX_DEEMPH_GEN1(x) (x << 16) ++#define PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) (x << 8) ++#define PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) (x << 0) ++ ++#define PCIE20_PARF_PCS_SWING 0x38 ++#define PCS_SWING_TX_SWING_FULL(x) (x << 8) ++#define PCS_SWING_TX_SWING_LOW(x) (x << 0) ++ + #define PCIE20_PARF_PHY_CTRL 0x40 ++#define PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK (0x1f << 16) ++#define PHY_CTRL_PHY_TX0_TERM_OFFSET(x) (x << 16) ++ + #define PCIE20_PARF_PHY_REFCLK 0x4C ++#define REF_SSP_EN BIT(16) ++#define REF_USE_PAD BIT(12) ++ ++#define PCIE20_PARF_CONFIG_BITS 0x50 ++#define PHY_RX0_EQ(x) (x << 24) ++ + #define PCIE20_PARF_DBI_BASE_ADDR 0x168 + #define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c + #define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 +@@ -39,9 +84,6 @@ + #define PCIE20_ELBI_SYS_STTS 0x08 + #define XMLH_LINK_UP BIT(10) + +-#define PCIE20_CAP 0x70 +-#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) +- + #define PERST_DELAY_MIN_US 1000 + #define PERST_DELAY_MAX_US 1005 + +@@ -56,14 +98,18 @@ struct qcom_pcie_resources_v0 { + struct clk *iface_clk; + struct clk *core_clk; + struct clk *phy_clk; ++ struct clk *aux_clk; ++ struct clk *ref_clk; + struct reset_control *pci_reset; + struct reset_control *axi_reset; + struct reset_control *ahb_reset; + struct reset_control *por_reset; + struct reset_control *phy_reset; ++ struct reset_control *ext_reset; + struct regulator *vdda; + struct regulator *vdda_phy; + struct regulator *vdda_refclk; ++ uint8_t phy_tx0_term_offset; + }; + + struct qcom_pcie_resources_v1 { +@@ -156,10 +202,13 @@ static void qcom_pcie_disable_resources_ + reset_control_assert(res->axi_reset); + reset_control_assert(res->ahb_reset); + reset_control_assert(res->por_reset); +- reset_control_assert(res->pci_reset); ++ reset_control_assert(res->phy_reset); ++ reset_control_assert(res->ext_reset); + clk_disable_unprepare(res->iface_clk); + clk_disable_unprepare(res->core_clk); + clk_disable_unprepare(res->phy_clk); ++ clk_disable_unprepare(res->aux_clk); ++ clk_disable_unprepare(res->ref_clk); + regulator_disable(res->vdda); + regulator_disable(res->vdda_phy); + regulator_disable(res->vdda_refclk); +@@ -201,6 +250,12 @@ static int qcom_pcie_enable_resources_v0 + goto err_vdda_phy; + } + ++ ret = reset_control_deassert(res->ext_reset); ++ if (ret) { ++ dev_err(dev, "cannot assert ext reset\n"); ++ goto err_reset_ext; ++ } ++ + ret = clk_prepare_enable(res->iface_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable iface clock\n"); +@@ -219,6 +274,18 @@ static int qcom_pcie_enable_resources_v0 + goto err_clk_phy; + } + ++ ret = clk_prepare_enable(res->aux_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable aux clock\n"); ++ goto err_clk_aux; ++ } ++ ++ ret = clk_prepare_enable(res->ref_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable ref clock\n"); ++ goto err_clk_ref; ++ } ++ + ret = reset_control_deassert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot deassert ahb reset\n"); +@@ -228,12 +295,18 @@ static int qcom_pcie_enable_resources_v0 + return 0; + + err_reset_ahb: ++ clk_disable_unprepare(res->ref_clk); ++err_clk_ref: ++ clk_disable_unprepare(res->aux_clk); ++err_clk_aux: + clk_disable_unprepare(res->phy_clk); + err_clk_phy: + clk_disable_unprepare(res->core_clk); + err_clk_core: + clk_disable_unprepare(res->iface_clk); + err_iface: ++ reset_control_assert(res->ext_reset); ++err_reset_ext: + regulator_disable(res->vdda_phy); + err_vdda_phy: + regulator_disable(res->vdda_refclk); +@@ -329,6 +402,14 @@ static int qcom_pcie_get_resources_v0(st + if (IS_ERR(res->phy_clk)) + return PTR_ERR(res->phy_clk); + ++ res->aux_clk = devm_clk_get(dev, "aux"); ++ if (IS_ERR(res->aux_clk)) ++ return PTR_ERR(res->aux_clk); ++ ++ res->ref_clk = devm_clk_get(dev, "ref"); ++ if (IS_ERR(res->ref_clk)) ++ return PTR_ERR(res->ref_clk); ++ + res->pci_reset = devm_reset_control_get(dev, "pci"); + if (IS_ERR(res->pci_reset)) + return PTR_ERR(res->pci_reset); +@@ -349,6 +430,14 @@ static int qcom_pcie_get_resources_v0(st + if (IS_ERR(res->phy_reset)) + return PTR_ERR(res->phy_reset); + ++ res->ext_reset = devm_reset_control_get(dev, "ext"); ++ if (IS_ERR(res->ext_reset)) ++ return PTR_ERR(res->ext_reset); ++ ++ if (of_property_read_u8(dev->of_node, "phy-tx0-term-offset", ++ &res->phy_tx0_term_offset)) ++ res->phy_tx0_term_offset = 0; ++ + return 0; + } + +@@ -461,6 +550,57 @@ err_res: + qcom_pcie_disable_resources_v1(pcie); + } + ++static void qcom_pcie_prog_viewport_cfg0(struct qcom_pcie *pcie, u32 busdev) ++{ ++ struct pcie_port *pp = &pcie->pp; ++ ++ /* ++ * program and enable address translation region 0 (device config ++ * address space); region type config; ++ * axi config address range to device config address range ++ */ ++ writel(PCIE20_PLR_IATU_REGION_OUTBOUND | ++ PCIE20_PLR_IATU_REGION_INDEX(0), ++ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT); ++ ++ writel(PCIE20_PLR_IATU_TYPE_CFG0, pcie->dbi + PCIE20_PLR_IATU_CTRL1); ++ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2); ++ writel(pp->cfg0_mod_base, pcie->dbi + PCIE20_PLR_IATU_LBAR); ++ writel((pp->cfg0_mod_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR); ++ writel((pp->cfg0_mod_base + pp->cfg0_size - 1), ++ pcie->dbi + PCIE20_PLR_IATU_LAR); ++ writel(busdev, pcie->dbi + PCIE20_PLR_IATU_LTAR); ++ writel(0, pcie->dbi + PCIE20_PLR_IATU_UTAR); ++} ++ ++static void qcom_pcie_prog_viewport_mem2_outbound(struct qcom_pcie *pcie) ++{ ++ struct pcie_port *pp = &pcie->pp; ++ ++ /* ++ * program and enable address translation region 2 (device resource ++ * address space); region type memory; ++ * axi device bar address range to device bar address range ++ */ ++ writel(PCIE20_PLR_IATU_REGION_OUTBOUND | ++ PCIE20_PLR_IATU_REGION_INDEX(2), ++ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT); ++ ++ writel(PCIE20_PLR_IATU_TYPE_MEM, pcie->dbi + PCIE20_PLR_IATU_CTRL1); ++ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2); ++ writel(pp->mem_mod_base, pcie->dbi + PCIE20_PLR_IATU_LBAR); ++ writel((pp->mem_mod_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR); ++ writel(pp->mem_mod_base + pp->mem_size - 1, ++ pcie->dbi + PCIE20_PLR_IATU_LAR); ++ writel(pp->mem_bus_addr, pcie->dbi + PCIE20_PLR_IATU_LTAR); ++ writel(upper_32_bits(pp->mem_bus_addr), ++ pcie->dbi + PCIE20_PLR_IATU_UTAR); ++ ++ /* 1K PCIE buffer setting */ ++ writel(0x3, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL0); ++ writel(0x1, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL1); ++} ++ + static void qcom_pcie_host_init_v0(struct pcie_port *pp) + { + struct qcom_pcie *pcie = to_qcom_pcie(pp); +@@ -476,9 +616,26 @@ static void qcom_pcie_host_init_v0(struc + + writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); + +- /* enable external reference clock */ +- writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); ++ /* Set Tx termination offset */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, ++ PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK, ++ PHY_CTRL_PHY_TX0_TERM_OFFSET(res->phy_tx0_term_offset)); ++ ++ /* PARF programming */ ++ writel(PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) | ++ PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) | ++ PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22), ++ pcie->parf + PCIE20_PARF_PCS_DEEMPH); ++ writel(PCS_SWING_TX_SWING_FULL(0x78) | ++ PCS_SWING_TX_SWING_LOW(0x78), ++ pcie->parf + PCIE20_PARF_PCS_SWING); ++ writel(PHY_RX0_EQ(0x4), pcie->parf + PCIE20_PARF_CONFIG_BITS); ++ ++ /* Enable reference clock */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, ++ REF_USE_PAD, REF_SSP_EN); + ++ /* De-assert PHY, PCIe, POR and AXI resets */ + ret = reset_control_deassert(res->phy_reset); + if (ret) { + dev_err(dev, "cannot deassert phy reset\n"); +@@ -517,6 +674,9 @@ static void qcom_pcie_host_init_v0(struc + if (ret) + goto err; + ++ qcom_pcie_prog_viewport_cfg0(pcie, MSM_PCIE_DEV_CFG_ADDR); ++ qcom_pcie_prog_viewport_mem2_outbound(pcie); ++ + return; + err: + qcom_ep_reset_assert(pcie); diff --git a/target/linux/ipq806x/patches-3.18/115-add-pcie-aux-clk-dts.patch b/target/linux/ipq806x/patches-3.18/115-add-pcie-aux-clk-dts.patch new file mode 100644 index 0000000..83c5f55 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/115-add-pcie-aux-clk-dts.patch @@ -0,0 +1,80 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -341,15 +341,21 @@ + + clocks = <&gcc PCIE_A_CLK>, + <&gcc PCIE_H_CLK>, +- <&gcc PCIE_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_PHY_CLK>, ++ <&gcc PCIE_AUX_CLK>, ++ <&gcc PCIE_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_ACLK_RESET>, + <&gcc PCIE_HCLK_RESET>, + <&gcc PCIE_POR_RESET>, + <&gcc PCIE_PCI_RESET>, +- <&gcc PCIE_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_PHY_RESET>, ++ <&gcc PCIE_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + status = "disabled"; + }; +@@ -382,15 +388,21 @@ + + clocks = <&gcc PCIE_1_A_CLK>, + <&gcc PCIE_1_H_CLK>, +- <&gcc PCIE_1_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_1_PHY_CLK>, ++ <&gcc PCIE_1_AUX_CLK>, ++ <&gcc PCIE_1_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_1_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_1_ACLK_RESET>, + <&gcc PCIE_1_HCLK_RESET>, + <&gcc PCIE_1_POR_RESET>, + <&gcc PCIE_1_PCI_RESET>, +- <&gcc PCIE_1_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_1_PHY_RESET>, ++ <&gcc PCIE_1_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + status = "disabled"; + }; +@@ -423,15 +435,21 @@ + + clocks = <&gcc PCIE_2_A_CLK>, + <&gcc PCIE_2_H_CLK>, +- <&gcc PCIE_2_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_2_PHY_CLK>, ++ <&gcc PCIE_2_AUX_CLK>, ++ <&gcc PCIE_2_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_2_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_2_ACLK_RESET>, + <&gcc PCIE_2_HCLK_RESET>, + <&gcc PCIE_2_POR_RESET>, + <&gcc PCIE_2_PCI_RESET>, +- <&gcc PCIE_2_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_2_PHY_RESET>, ++ <&gcc PCIE_2_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + status = "disabled"; + }; diff --git a/target/linux/ipq806x/patches-3.18/120-mfd-qcom-rpm-Driver-for-the-Qualcomm-RPM.patch b/target/linux/ipq806x/patches-3.18/120-mfd-qcom-rpm-Driver-for-the-Qualcomm-RPM.patch new file mode 100644 index 0000000..d819142 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/120-mfd-qcom-rpm-Driver-for-the-Qualcomm-RPM.patch @@ -0,0 +1,654 @@ +From 58e214382bdd1eb48c5a3519182bddcb26edabad Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Wed, 26 Nov 2014 13:51:00 -0800 +Subject: [PATCH] mfd: qcom-rpm: Driver for the Qualcomm RPM + +Driver for the Resource Power Manager (RPM) found in Qualcomm 8660, 8960 +and 8064 based devices. The driver exposes resources that child drivers +can operate on; to implementing regulator, clock and bus frequency +drivers. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Lee Jones +--- + drivers/mfd/Kconfig | 14 ++ + drivers/mfd/Makefile | 1 + + drivers/mfd/qcom_rpm.c | 581 +++++++++++++++++++++++++++++++++++++++++++ + include/linux/mfd/qcom_rpm.h | 13 + + 4 files changed, 609 insertions(+) + create mode 100644 drivers/mfd/qcom_rpm.c + create mode 100644 include/linux/mfd/qcom_rpm.h + +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -567,6 +567,20 @@ config MFD_PM8921_CORE + Say M here if you want to include support for PM8921 chip as a module. + This will build a module called "pm8921-core". + ++config MFD_QCOM_RPM ++ tristate "Qualcomm Resource Power Manager (RPM)" ++ depends on ARCH_QCOM && OF ++ help ++ If you say yes to this option, support will be included for the ++ Resource Power Manager system found in the Qualcomm 8660, 8960 and ++ 8064 based devices. ++ ++ This is required to access many regulators, clocks and bus ++ frequencies controlled by the RPM on these devices. ++ ++ Say M here if you want to include support for the Qualcomm RPM as a ++ module. This will build a module called "qcom_rpm". ++ + config MFD_SPMI_PMIC + tristate "Qualcomm SPMI PMICs" + depends on ARCH_QCOM || COMPILE_TEST +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -153,6 +153,7 @@ obj-$(CONFIG_MFD_SI476X_CORE) += si476x- + obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o + obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o + obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o ssbi.o ++obj-$(CONFIG_MFD_QCOM_RPM) += qcom_rpm.o + obj-$(CONFIG_MFD_SPMI_PMIC) += qcom-spmi-pmic.o + obj-$(CONFIG_TPS65911_COMPARATOR) += tps65911-comparator.o + obj-$(CONFIG_MFD_TPS65090) += tps65090.o +--- /dev/null ++++ b/drivers/mfd/qcom_rpm.c +@@ -0,0 +1,581 @@ ++/* ++ * Copyright (c) 2014, Sony Mobile Communications AB. ++ * Copyright (c) 2013, The Linux Foundation. All rights reserved. ++ * Author: Bjorn Andersson ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++struct qcom_rpm_resource { ++ unsigned target_id; ++ unsigned status_id; ++ unsigned select_id; ++ unsigned size; ++}; ++ ++struct qcom_rpm_data { ++ u32 version; ++ const struct qcom_rpm_resource *resource_table; ++ unsigned n_resources; ++}; ++ ++struct qcom_rpm { ++ struct device *dev; ++ struct regmap *ipc_regmap; ++ unsigned ipc_offset; ++ unsigned ipc_bit; ++ ++ struct completion ack; ++ struct mutex lock; ++ ++ void __iomem *status_regs; ++ void __iomem *ctrl_regs; ++ void __iomem *req_regs; ++ ++ u32 ack_status; ++ ++ const struct qcom_rpm_data *data; ++}; ++ ++#define RPM_STATUS_REG(rpm, i) ((rpm)->status_regs + (i) * 4) ++#define RPM_CTRL_REG(rpm, i) ((rpm)->ctrl_regs + (i) * 4) ++#define RPM_REQ_REG(rpm, i) ((rpm)->req_regs + (i) * 4) ++ ++#define RPM_REQUEST_TIMEOUT (5 * HZ) ++ ++#define RPM_REQUEST_CONTEXT 3 ++#define RPM_REQ_SELECT 11 ++#define RPM_ACK_CONTEXT 15 ++#define RPM_ACK_SELECTOR 23 ++#define RPM_SELECT_SIZE 7 ++ ++#define RPM_NOTIFICATION BIT(30) ++#define RPM_REJECTED BIT(31) ++ ++#define RPM_SIGNAL BIT(2) ++ ++static const struct qcom_rpm_resource apq8064_rpm_resource_table[] = { ++ [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 }, ++ [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 }, ++ [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 }, ++ [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 }, ++ [QCOM_RPM_MM_FABRIC_CLK] = { 29, 13, 10, 1 }, ++ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 }, ++ [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 }, ++ [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 }, ++ [QCOM_RPM_MMFPB_CLK] = { 33, 17, 14, 1 }, ++ [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 }, ++ [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 1 }, ++ [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 1 }, ++ [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 }, ++ [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 }, ++ [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 1 }, ++ [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 1 }, ++ [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 }, ++ [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 30 }, ++ [QCOM_RPM_MM_FABRIC_HALT] = { 89, 27, 26, 1 }, ++ [QCOM_RPM_MM_FABRIC_MODE] = { 91, 28, 27, 1 }, ++ [QCOM_RPM_MM_FABRIC_IOCTL] = { 94, 29, 28, 1 }, ++ [QCOM_RPM_MM_FABRIC_ARB] = { 95, 30, 29, 21 }, ++ [QCOM_RPM_PM8921_SMPS1] = { 116, 31, 30, 2 }, ++ [QCOM_RPM_PM8921_SMPS2] = { 118, 33, 31, 2 }, ++ [QCOM_RPM_PM8921_SMPS3] = { 120, 35, 32, 2 }, ++ [QCOM_RPM_PM8921_SMPS4] = { 122, 37, 33, 2 }, ++ [QCOM_RPM_PM8921_SMPS5] = { 124, 39, 34, 2 }, ++ [QCOM_RPM_PM8921_SMPS6] = { 126, 41, 35, 2 }, ++ [QCOM_RPM_PM8921_SMPS7] = { 128, 43, 36, 2 }, ++ [QCOM_RPM_PM8921_SMPS8] = { 130, 45, 37, 2 }, ++ [QCOM_RPM_PM8921_LDO1] = { 132, 47, 38, 2 }, ++ [QCOM_RPM_PM8921_LDO2] = { 134, 49, 39, 2 }, ++ [QCOM_RPM_PM8921_LDO3] = { 136, 51, 40, 2 }, ++ [QCOM_RPM_PM8921_LDO4] = { 138, 53, 41, 2 }, ++ [QCOM_RPM_PM8921_LDO5] = { 140, 55, 42, 2 }, ++ [QCOM_RPM_PM8921_LDO6] = { 142, 57, 43, 2 }, ++ [QCOM_RPM_PM8921_LDO7] = { 144, 59, 44, 2 }, ++ [QCOM_RPM_PM8921_LDO8] = { 146, 61, 45, 2 }, ++ [QCOM_RPM_PM8921_LDO9] = { 148, 63, 46, 2 }, ++ [QCOM_RPM_PM8921_LDO10] = { 150, 65, 47, 2 }, ++ [QCOM_RPM_PM8921_LDO11] = { 152, 67, 48, 2 }, ++ [QCOM_RPM_PM8921_LDO12] = { 154, 69, 49, 2 }, ++ [QCOM_RPM_PM8921_LDO13] = { 156, 71, 50, 2 }, ++ [QCOM_RPM_PM8921_LDO14] = { 158, 73, 51, 2 }, ++ [QCOM_RPM_PM8921_LDO15] = { 160, 75, 52, 2 }, ++ [QCOM_RPM_PM8921_LDO16] = { 162, 77, 53, 2 }, ++ [QCOM_RPM_PM8921_LDO17] = { 164, 79, 54, 2 }, ++ [QCOM_RPM_PM8921_LDO18] = { 166, 81, 55, 2 }, ++ [QCOM_RPM_PM8921_LDO19] = { 168, 83, 56, 2 }, ++ [QCOM_RPM_PM8921_LDO20] = { 170, 85, 57, 2 }, ++ [QCOM_RPM_PM8921_LDO21] = { 172, 87, 58, 2 }, ++ [QCOM_RPM_PM8921_LDO22] = { 174, 89, 59, 2 }, ++ [QCOM_RPM_PM8921_LDO23] = { 176, 91, 60, 2 }, ++ [QCOM_RPM_PM8921_LDO24] = { 178, 93, 61, 2 }, ++ [QCOM_RPM_PM8921_LDO25] = { 180, 95, 62, 2 }, ++ [QCOM_RPM_PM8921_LDO26] = { 182, 97, 63, 2 }, ++ [QCOM_RPM_PM8921_LDO27] = { 184, 99, 64, 2 }, ++ [QCOM_RPM_PM8921_LDO28] = { 186, 101, 65, 2 }, ++ [QCOM_RPM_PM8921_LDO29] = { 188, 103, 66, 2 }, ++ [QCOM_RPM_PM8921_CLK1] = { 190, 105, 67, 2 }, ++ [QCOM_RPM_PM8921_CLK2] = { 192, 107, 68, 2 }, ++ [QCOM_RPM_PM8921_LVS1] = { 194, 109, 69, 1 }, ++ [QCOM_RPM_PM8921_LVS2] = { 195, 110, 70, 1 }, ++ [QCOM_RPM_PM8921_LVS3] = { 196, 111, 71, 1 }, ++ [QCOM_RPM_PM8921_LVS4] = { 197, 112, 72, 1 }, ++ [QCOM_RPM_PM8921_LVS5] = { 198, 113, 73, 1 }, ++ [QCOM_RPM_PM8921_LVS6] = { 199, 114, 74, 1 }, ++ [QCOM_RPM_PM8921_LVS7] = { 200, 115, 75, 1 }, ++ [QCOM_RPM_PM8821_SMPS1] = { 201, 116, 76, 2 }, ++ [QCOM_RPM_PM8821_SMPS2] = { 203, 118, 77, 2 }, ++ [QCOM_RPM_PM8821_LDO1] = { 205, 120, 78, 2 }, ++ [QCOM_RPM_PM8921_NCP] = { 207, 122, 80, 2 }, ++ [QCOM_RPM_CXO_BUFFERS] = { 209, 124, 81, 1 }, ++ [QCOM_RPM_USB_OTG_SWITCH] = { 210, 125, 82, 1 }, ++ [QCOM_RPM_HDMI_SWITCH] = { 211, 126, 83, 1 }, ++ [QCOM_RPM_DDR_DMM] = { 212, 127, 84, 2 }, ++ [QCOM_RPM_VDDMIN_GPIO] = { 215, 131, 89, 1 }, ++}; ++ ++static const struct qcom_rpm_data apq8064_template = { ++ .version = 3, ++ .resource_table = apq8064_rpm_resource_table, ++ .n_resources = ARRAY_SIZE(apq8064_rpm_resource_table), ++}; ++ ++static const struct qcom_rpm_resource msm8660_rpm_resource_table[] = { ++ [QCOM_RPM_CXO_CLK] = { 32, 12, 5, 1 }, ++ [QCOM_RPM_PXO_CLK] = { 33, 13, 6, 1 }, ++ [QCOM_RPM_PLL_4] = { 34, 14, 7, 1 }, ++ [QCOM_RPM_APPS_FABRIC_CLK] = { 35, 15, 8, 1 }, ++ [QCOM_RPM_SYS_FABRIC_CLK] = { 36, 16, 9, 1 }, ++ [QCOM_RPM_MM_FABRIC_CLK] = { 37, 17, 10, 1 }, ++ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 38, 18, 11, 1 }, ++ [QCOM_RPM_SFPB_CLK] = { 39, 19, 12, 1 }, ++ [QCOM_RPM_CFPB_CLK] = { 40, 20, 13, 1 }, ++ [QCOM_RPM_MMFPB_CLK] = { 41, 21, 14, 1 }, ++ [QCOM_RPM_SMI_CLK] = { 42, 22, 15, 1 }, ++ [QCOM_RPM_EBI1_CLK] = { 43, 23, 16, 1 }, ++ [QCOM_RPM_APPS_L2_CACHE_CTL] = { 44, 24, 17, 1 }, ++ [QCOM_RPM_APPS_FABRIC_HALT] = { 45, 25, 18, 2 }, ++ [QCOM_RPM_APPS_FABRIC_MODE] = { 47, 26, 19, 3 }, ++ [QCOM_RPM_APPS_FABRIC_ARB] = { 51, 28, 21, 6 }, ++ [QCOM_RPM_SYS_FABRIC_HALT] = { 63, 29, 22, 2 }, ++ [QCOM_RPM_SYS_FABRIC_MODE] = { 65, 30, 23, 3 }, ++ [QCOM_RPM_SYS_FABRIC_ARB] = { 69, 32, 25, 22 }, ++ [QCOM_RPM_MM_FABRIC_HALT] = { 105, 33, 26, 2 }, ++ [QCOM_RPM_MM_FABRIC_MODE] = { 107, 34, 27, 3 }, ++ [QCOM_RPM_MM_FABRIC_ARB] = { 111, 36, 29, 23 }, ++ [QCOM_RPM_PM8901_SMPS0] = { 134, 37, 30, 2 }, ++ [QCOM_RPM_PM8901_SMPS1] = { 136, 39, 31, 2 }, ++ [QCOM_RPM_PM8901_SMPS2] = { 138, 41, 32, 2 }, ++ [QCOM_RPM_PM8901_SMPS3] = { 140, 43, 33, 2 }, ++ [QCOM_RPM_PM8901_SMPS4] = { 142, 45, 34, 2 }, ++ [QCOM_RPM_PM8901_LDO0] = { 144, 47, 35, 2 }, ++ [QCOM_RPM_PM8901_LDO1] = { 146, 49, 36, 2 }, ++ [QCOM_RPM_PM8901_LDO2] = { 148, 51, 37, 2 }, ++ [QCOM_RPM_PM8901_LDO3] = { 150, 53, 38, 2 }, ++ [QCOM_RPM_PM8901_LDO4] = { 152, 55, 39, 2 }, ++ [QCOM_RPM_PM8901_LDO5] = { 154, 57, 40, 2 }, ++ [QCOM_RPM_PM8901_LDO6] = { 156, 59, 41, 2 }, ++ [QCOM_RPM_PM8901_LVS0] = { 158, 61, 42, 1 }, ++ [QCOM_RPM_PM8901_LVS1] = { 159, 62, 43, 1 }, ++ [QCOM_RPM_PM8901_LVS2] = { 160, 63, 44, 1 }, ++ [QCOM_RPM_PM8901_LVS3] = { 161, 64, 45, 1 }, ++ [QCOM_RPM_PM8901_MVS] = { 162, 65, 46, 1 }, ++ [QCOM_RPM_PM8058_SMPS0] = { 163, 66, 47, 2 }, ++ [QCOM_RPM_PM8058_SMPS1] = { 165, 68, 48, 2 }, ++ [QCOM_RPM_PM8058_SMPS2] = { 167, 70, 49, 2 }, ++ [QCOM_RPM_PM8058_SMPS3] = { 169, 72, 50, 2 }, ++ [QCOM_RPM_PM8058_SMPS4] = { 171, 74, 51, 2 }, ++ [QCOM_RPM_PM8058_LDO0] = { 173, 76, 52, 2 }, ++ [QCOM_RPM_PM8058_LDO1] = { 175, 78, 53, 2 }, ++ [QCOM_RPM_PM8058_LDO2] = { 177, 80, 54, 2 }, ++ [QCOM_RPM_PM8058_LDO3] = { 179, 82, 55, 2 }, ++ [QCOM_RPM_PM8058_LDO4] = { 181, 84, 56, 2 }, ++ [QCOM_RPM_PM8058_LDO5] = { 183, 86, 57, 2 }, ++ [QCOM_RPM_PM8058_LDO6] = { 185, 88, 58, 2 }, ++ [QCOM_RPM_PM8058_LDO7] = { 187, 90, 59, 2 }, ++ [QCOM_RPM_PM8058_LDO8] = { 189, 92, 60, 2 }, ++ [QCOM_RPM_PM8058_LDO9] = { 191, 94, 61, 2 }, ++ [QCOM_RPM_PM8058_LDO10] = { 193, 96, 62, 2 }, ++ [QCOM_RPM_PM8058_LDO11] = { 195, 98, 63, 2 }, ++ [QCOM_RPM_PM8058_LDO12] = { 197, 100, 64, 2 }, ++ [QCOM_RPM_PM8058_LDO13] = { 199, 102, 65, 2 }, ++ [QCOM_RPM_PM8058_LDO14] = { 201, 104, 66, 2 }, ++ [QCOM_RPM_PM8058_LDO15] = { 203, 106, 67, 2 }, ++ [QCOM_RPM_PM8058_LDO16] = { 205, 108, 68, 2 }, ++ [QCOM_RPM_PM8058_LDO17] = { 207, 110, 69, 2 }, ++ [QCOM_RPM_PM8058_LDO18] = { 209, 112, 70, 2 }, ++ [QCOM_RPM_PM8058_LDO19] = { 211, 114, 71, 2 }, ++ [QCOM_RPM_PM8058_LDO20] = { 213, 116, 72, 2 }, ++ [QCOM_RPM_PM8058_LDO21] = { 215, 118, 73, 2 }, ++ [QCOM_RPM_PM8058_LDO22] = { 217, 120, 74, 2 }, ++ [QCOM_RPM_PM8058_LDO23] = { 219, 122, 75, 2 }, ++ [QCOM_RPM_PM8058_LDO24] = { 221, 124, 76, 2 }, ++ [QCOM_RPM_PM8058_LDO25] = { 223, 126, 77, 2 }, ++ [QCOM_RPM_PM8058_LVS0] = { 225, 128, 78, 1 }, ++ [QCOM_RPM_PM8058_LVS1] = { 226, 129, 79, 1 }, ++ [QCOM_RPM_PM8058_NCP] = { 227, 130, 80, 2 }, ++ [QCOM_RPM_CXO_BUFFERS] = { 229, 132, 81, 1 }, ++}; ++ ++static const struct qcom_rpm_data msm8660_template = { ++ .version = 2, ++ .resource_table = msm8660_rpm_resource_table, ++ .n_resources = ARRAY_SIZE(msm8660_rpm_resource_table), ++}; ++ ++static const struct qcom_rpm_resource msm8960_rpm_resource_table[] = { ++ [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 }, ++ [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 }, ++ [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 }, ++ [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 }, ++ [QCOM_RPM_MM_FABRIC_CLK] = { 29, 13, 10, 1 }, ++ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 }, ++ [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 }, ++ [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 }, ++ [QCOM_RPM_MMFPB_CLK] = { 33, 17, 14, 1 }, ++ [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 }, ++ [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 1 }, ++ [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 1 }, ++ [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 }, ++ [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 }, ++ [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 1 }, ++ [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 1 }, ++ [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 }, ++ [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 29 }, ++ [QCOM_RPM_MM_FABRIC_HALT] = { 88, 27, 26, 1 }, ++ [QCOM_RPM_MM_FABRIC_MODE] = { 90, 28, 27, 1 }, ++ [QCOM_RPM_MM_FABRIC_IOCTL] = { 93, 29, 28, 1 }, ++ [QCOM_RPM_MM_FABRIC_ARB] = { 94, 30, 29, 23 }, ++ [QCOM_RPM_PM8921_SMPS1] = { 117, 31, 30, 2 }, ++ [QCOM_RPM_PM8921_SMPS2] = { 119, 33, 31, 2 }, ++ [QCOM_RPM_PM8921_SMPS3] = { 121, 35, 32, 2 }, ++ [QCOM_RPM_PM8921_SMPS4] = { 123, 37, 33, 2 }, ++ [QCOM_RPM_PM8921_SMPS5] = { 125, 39, 34, 2 }, ++ [QCOM_RPM_PM8921_SMPS6] = { 127, 41, 35, 2 }, ++ [QCOM_RPM_PM8921_SMPS7] = { 129, 43, 36, 2 }, ++ [QCOM_RPM_PM8921_SMPS8] = { 131, 45, 37, 2 }, ++ [QCOM_RPM_PM8921_LDO1] = { 133, 47, 38, 2 }, ++ [QCOM_RPM_PM8921_LDO2] = { 135, 49, 39, 2 }, ++ [QCOM_RPM_PM8921_LDO3] = { 137, 51, 40, 2 }, ++ [QCOM_RPM_PM8921_LDO4] = { 139, 53, 41, 2 }, ++ [QCOM_RPM_PM8921_LDO5] = { 141, 55, 42, 2 }, ++ [QCOM_RPM_PM8921_LDO6] = { 143, 57, 43, 2 }, ++ [QCOM_RPM_PM8921_LDO7] = { 145, 59, 44, 2 }, ++ [QCOM_RPM_PM8921_LDO8] = { 147, 61, 45, 2 }, ++ [QCOM_RPM_PM8921_LDO9] = { 149, 63, 46, 2 }, ++ [QCOM_RPM_PM8921_LDO10] = { 151, 65, 47, 2 }, ++ [QCOM_RPM_PM8921_LDO11] = { 153, 67, 48, 2 }, ++ [QCOM_RPM_PM8921_LDO12] = { 155, 69, 49, 2 }, ++ [QCOM_RPM_PM8921_LDO13] = { 157, 71, 50, 2 }, ++ [QCOM_RPM_PM8921_LDO14] = { 159, 73, 51, 2 }, ++ [QCOM_RPM_PM8921_LDO15] = { 161, 75, 52, 2 }, ++ [QCOM_RPM_PM8921_LDO16] = { 163, 77, 53, 2 }, ++ [QCOM_RPM_PM8921_LDO17] = { 165, 79, 54, 2 }, ++ [QCOM_RPM_PM8921_LDO18] = { 167, 81, 55, 2 }, ++ [QCOM_RPM_PM8921_LDO19] = { 169, 83, 56, 2 }, ++ [QCOM_RPM_PM8921_LDO20] = { 171, 85, 57, 2 }, ++ [QCOM_RPM_PM8921_LDO21] = { 173, 87, 58, 2 }, ++ [QCOM_RPM_PM8921_LDO22] = { 175, 89, 59, 2 }, ++ [QCOM_RPM_PM8921_LDO23] = { 177, 91, 60, 2 }, ++ [QCOM_RPM_PM8921_LDO24] = { 179, 93, 61, 2 }, ++ [QCOM_RPM_PM8921_LDO25] = { 181, 95, 62, 2 }, ++ [QCOM_RPM_PM8921_LDO26] = { 183, 97, 63, 2 }, ++ [QCOM_RPM_PM8921_LDO27] = { 185, 99, 64, 2 }, ++ [QCOM_RPM_PM8921_LDO28] = { 187, 101, 65, 2 }, ++ [QCOM_RPM_PM8921_LDO29] = { 189, 103, 66, 2 }, ++ [QCOM_RPM_PM8921_CLK1] = { 191, 105, 67, 2 }, ++ [QCOM_RPM_PM8921_CLK2] = { 193, 107, 68, 2 }, ++ [QCOM_RPM_PM8921_LVS1] = { 195, 109, 69, 1 }, ++ [QCOM_RPM_PM8921_LVS2] = { 196, 110, 70, 1 }, ++ [QCOM_RPM_PM8921_LVS3] = { 197, 111, 71, 1 }, ++ [QCOM_RPM_PM8921_LVS4] = { 198, 112, 72, 1 }, ++ [QCOM_RPM_PM8921_LVS5] = { 199, 113, 73, 1 }, ++ [QCOM_RPM_PM8921_LVS6] = { 200, 114, 74, 1 }, ++ [QCOM_RPM_PM8921_LVS7] = { 201, 115, 75, 1 }, ++ [QCOM_RPM_PM8921_NCP] = { 202, 116, 80, 2 }, ++ [QCOM_RPM_CXO_BUFFERS] = { 204, 118, 81, 1 }, ++ [QCOM_RPM_USB_OTG_SWITCH] = { 205, 119, 82, 1 }, ++ [QCOM_RPM_HDMI_SWITCH] = { 206, 120, 83, 1 }, ++ [QCOM_RPM_DDR_DMM] = { 207, 121, 84, 2 }, ++}; ++ ++static const struct qcom_rpm_data msm8960_template = { ++ .version = 3, ++ .resource_table = msm8960_rpm_resource_table, ++ .n_resources = ARRAY_SIZE(msm8960_rpm_resource_table), ++}; ++ ++static const struct of_device_id qcom_rpm_of_match[] = { ++ { .compatible = "qcom,rpm-apq8064", .data = &apq8064_template }, ++ { .compatible = "qcom,rpm-msm8660", .data = &msm8660_template }, ++ { .compatible = "qcom,rpm-msm8960", .data = &msm8960_template }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, qcom_rpm_of_match); ++ ++int qcom_rpm_write(struct qcom_rpm *rpm, ++ int state, ++ int resource, ++ u32 *buf, size_t count) ++{ ++ const struct qcom_rpm_resource *res; ++ const struct qcom_rpm_data *data = rpm->data; ++ u32 sel_mask[RPM_SELECT_SIZE] = { 0 }; ++ int left; ++ int ret = 0; ++ int i; ++ ++ if (WARN_ON(resource < 0 || resource >= data->n_resources)) ++ return -EINVAL; ++ ++ res = &data->resource_table[resource]; ++ if (WARN_ON(res->size != count)) ++ return -EINVAL; ++ ++ mutex_lock(&rpm->lock); ++ ++ for (i = 0; i < res->size; i++) ++ writel_relaxed(buf[i], RPM_REQ_REG(rpm, res->target_id + i)); ++ ++ bitmap_set((unsigned long *)sel_mask, res->select_id, 1); ++ for (i = 0; i < ARRAY_SIZE(sel_mask); i++) { ++ writel_relaxed(sel_mask[i], ++ RPM_CTRL_REG(rpm, RPM_REQ_SELECT + i)); ++ } ++ ++ writel_relaxed(BIT(state), RPM_CTRL_REG(rpm, RPM_REQUEST_CONTEXT)); ++ ++ reinit_completion(&rpm->ack); ++ regmap_write(rpm->ipc_regmap, rpm->ipc_offset, BIT(rpm->ipc_bit)); ++ ++ left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT); ++ if (!left) ++ ret = -ETIMEDOUT; ++ else if (rpm->ack_status & RPM_REJECTED) ++ ret = -EIO; ++ ++ mutex_unlock(&rpm->lock); ++ ++ return ret; ++} ++EXPORT_SYMBOL(qcom_rpm_write); ++ ++static irqreturn_t qcom_rpm_ack_interrupt(int irq, void *dev) ++{ ++ struct qcom_rpm *rpm = dev; ++ u32 ack; ++ int i; ++ ++ ack = readl_relaxed(RPM_CTRL_REG(rpm, RPM_ACK_CONTEXT)); ++ for (i = 0; i < RPM_SELECT_SIZE; i++) ++ writel_relaxed(0, RPM_CTRL_REG(rpm, RPM_ACK_SELECTOR + i)); ++ writel(0, RPM_CTRL_REG(rpm, RPM_ACK_CONTEXT)); ++ ++ if (ack & RPM_NOTIFICATION) { ++ dev_warn(rpm->dev, "ignoring notification!\n"); ++ } else { ++ rpm->ack_status = ack; ++ complete(&rpm->ack); ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t qcom_rpm_err_interrupt(int irq, void *dev) ++{ ++ struct qcom_rpm *rpm = dev; ++ ++ regmap_write(rpm->ipc_regmap, rpm->ipc_offset, BIT(rpm->ipc_bit)); ++ dev_err(rpm->dev, "RPM triggered fatal error\n"); ++ ++ return IRQ_HANDLED; ++} ++ ++static irqreturn_t qcom_rpm_wakeup_interrupt(int irq, void *dev) ++{ ++ return IRQ_HANDLED; ++} ++ ++static int qcom_rpm_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *match; ++ struct device_node *syscon_np; ++ struct resource *res; ++ struct qcom_rpm *rpm; ++ u32 fw_version[3]; ++ int irq_wakeup; ++ int irq_ack; ++ int irq_err; ++ int ret; ++ ++ rpm = devm_kzalloc(&pdev->dev, sizeof(*rpm), GFP_KERNEL); ++ if (!rpm) ++ return -ENOMEM; ++ ++ rpm->dev = &pdev->dev; ++ mutex_init(&rpm->lock); ++ init_completion(&rpm->ack); ++ ++ irq_ack = platform_get_irq_byname(pdev, "ack"); ++ if (irq_ack < 0) { ++ dev_err(&pdev->dev, "required ack interrupt missing\n"); ++ return irq_ack; ++ } ++ ++ irq_err = platform_get_irq_byname(pdev, "err"); ++ if (irq_err < 0) { ++ dev_err(&pdev->dev, "required err interrupt missing\n"); ++ return irq_err; ++ } ++ ++ irq_wakeup = platform_get_irq_byname(pdev, "wakeup"); ++ if (irq_wakeup < 0) { ++ dev_err(&pdev->dev, "required wakeup interrupt missing\n"); ++ return irq_wakeup; ++ } ++ ++ match = of_match_device(qcom_rpm_of_match, &pdev->dev); ++ rpm->data = match->data; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ rpm->status_regs = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(rpm->status_regs)) ++ return PTR_ERR(rpm->status_regs); ++ rpm->ctrl_regs = rpm->status_regs + 0x400; ++ rpm->req_regs = rpm->status_regs + 0x600; ++ ++ syscon_np = of_parse_phandle(pdev->dev.of_node, "qcom,ipc", 0); ++ if (!syscon_np) { ++ dev_err(&pdev->dev, "no qcom,ipc node\n"); ++ return -ENODEV; ++ } ++ ++ rpm->ipc_regmap = syscon_node_to_regmap(syscon_np); ++ if (IS_ERR(rpm->ipc_regmap)) ++ return PTR_ERR(rpm->ipc_regmap); ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,ipc", 1, ++ &rpm->ipc_offset); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "no offset in qcom,ipc\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "qcom,ipc", 2, ++ &rpm->ipc_bit); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "no bit in qcom,ipc\n"); ++ return -EINVAL; ++ } ++ ++ dev_set_drvdata(&pdev->dev, rpm); ++ ++ fw_version[0] = readl(RPM_STATUS_REG(rpm, 0)); ++ fw_version[1] = readl(RPM_STATUS_REG(rpm, 1)); ++ fw_version[2] = readl(RPM_STATUS_REG(rpm, 2)); ++ if (fw_version[0] != rpm->data->version) { ++ dev_err(&pdev->dev, ++ "RPM version %u.%u.%u incompatible with driver version %u", ++ fw_version[0], ++ fw_version[1], ++ fw_version[2], ++ rpm->data->version); ++ return -EFAULT; ++ } ++ ++ dev_info(&pdev->dev, "RPM firmware %u.%u.%u\n", fw_version[0], ++ fw_version[1], ++ fw_version[2]); ++ ++ ret = devm_request_irq(&pdev->dev, ++ irq_ack, ++ qcom_rpm_ack_interrupt, ++ IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND, ++ "qcom_rpm_ack", ++ rpm); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request ack interrupt\n"); ++ return ret; ++ } ++ ++ ret = irq_set_irq_wake(irq_ack, 1); ++ if (ret) ++ dev_warn(&pdev->dev, "failed to mark ack irq as wakeup\n"); ++ ++ ret = devm_request_irq(&pdev->dev, ++ irq_err, ++ qcom_rpm_err_interrupt, ++ IRQF_TRIGGER_RISING, ++ "qcom_rpm_err", ++ rpm); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request err interrupt\n"); ++ return ret; ++ } ++ ++ ret = devm_request_irq(&pdev->dev, ++ irq_wakeup, ++ qcom_rpm_wakeup_interrupt, ++ IRQF_TRIGGER_RISING, ++ "qcom_rpm_wakeup", ++ rpm); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request wakeup interrupt\n"); ++ return ret; ++ } ++ ++ ret = irq_set_irq_wake(irq_wakeup, 1); ++ if (ret) ++ dev_warn(&pdev->dev, "failed to mark wakeup irq as wakeup\n"); ++ ++ return of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); ++} ++ ++static int qcom_rpm_remove(struct platform_device *pdev) ++{ ++ of_platform_depopulate(&pdev->dev); ++ return 0; ++} ++ ++static struct platform_driver qcom_rpm_driver = { ++ .probe = qcom_rpm_probe, ++ .remove = qcom_rpm_remove, ++ .driver = { ++ .name = "qcom_rpm", ++ .of_match_table = qcom_rpm_of_match, ++ }, ++}; ++ ++static int __init qcom_rpm_init(void) ++{ ++ return platform_driver_register(&qcom_rpm_driver); ++} ++arch_initcall(qcom_rpm_init); ++ ++static void __exit qcom_rpm_exit(void) ++{ ++ platform_driver_unregister(&qcom_rpm_driver); ++} ++module_exit(qcom_rpm_exit) ++ ++MODULE_DESCRIPTION("Qualcomm Resource Power Manager driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_AUTHOR("Bjorn Andersson "); +--- /dev/null ++++ b/include/linux/mfd/qcom_rpm.h +@@ -0,0 +1,13 @@ ++#ifndef __QCOM_RPM_H__ ++#define __QCOM_RPM_H__ ++ ++#include ++ ++struct qcom_rpm; ++ ++#define QCOM_RPM_ACTIVE_STATE 0 ++#define QCOM_RPM_SLEEP_STATE 1 ++ ++int qcom_rpm_write(struct qcom_rpm *rpm, int state, int resource, u32 *buf, size_t count); ++ ++#endif diff --git a/target/linux/ipq806x/patches-3.18/121-mfd-qcom_rpm-Add-support-for-IPQ8064.patch b/target/linux/ipq806x/patches-3.18/121-mfd-qcom_rpm-Add-support-for-IPQ8064.patch new file mode 100644 index 0000000..e5e9e4e --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/121-mfd-qcom_rpm-Add-support-for-IPQ8064.patch @@ -0,0 +1,71 @@ +From 4d54b0adfa67476e6509bc8646b9dbac642e8a29 Mon Sep 17 00:00:00 2001 +From: Josh Cartwright +Date: Thu, 26 Mar 2015 11:29:26 -0700 +Subject: [PATCH] mfd: qcom_rpm: Add support for IPQ8064 + +The IPQ8064 also includes an RPM following the same message structure as +other chips. In addition, it supports a few new resource types to +support the NSS fabric clocks and the SMB208/SMB209 regulators found on +the reference boards. + +Signed-off-by: Josh Cartwright +Signed-off-by: Stephen Boyd +Signed-off-by: Lee Jones +--- + drivers/mfd/qcom_rpm.c | 41 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 41 insertions(+) + +--- a/drivers/mfd/qcom_rpm.c ++++ b/drivers/mfd/qcom_rpm.c +@@ -323,10 +323,51 @@ static const struct qcom_rpm_data msm896 + .n_resources = ARRAY_SIZE(msm8960_rpm_resource_table), + }; + ++static const struct qcom_rpm_resource ipq806x_rpm_resource_table[] = { ++ [QCOM_RPM_CXO_CLK] = { 25, 9, 5, 1 }, ++ [QCOM_RPM_PXO_CLK] = { 26, 10, 6, 1 }, ++ [QCOM_RPM_APPS_FABRIC_CLK] = { 27, 11, 8, 1 }, ++ [QCOM_RPM_SYS_FABRIC_CLK] = { 28, 12, 9, 1 }, ++ [QCOM_RPM_NSS_FABRIC_0_CLK] = { 29, 13, 10, 1 }, ++ [QCOM_RPM_DAYTONA_FABRIC_CLK] = { 30, 14, 11, 1 }, ++ [QCOM_RPM_SFPB_CLK] = { 31, 15, 12, 1 }, ++ [QCOM_RPM_CFPB_CLK] = { 32, 16, 13, 1 }, ++ [QCOM_RPM_NSS_FABRIC_1_CLK] = { 33, 17, 14, 1 }, ++ [QCOM_RPM_EBI1_CLK] = { 34, 18, 16, 1 }, ++ [QCOM_RPM_APPS_FABRIC_HALT] = { 35, 19, 18, 2 }, ++ [QCOM_RPM_APPS_FABRIC_MODE] = { 37, 20, 19, 3 }, ++ [QCOM_RPM_APPS_FABRIC_IOCTL] = { 40, 21, 20, 1 }, ++ [QCOM_RPM_APPS_FABRIC_ARB] = { 41, 22, 21, 12 }, ++ [QCOM_RPM_SYS_FABRIC_HALT] = { 53, 23, 22, 2 }, ++ [QCOM_RPM_SYS_FABRIC_MODE] = { 55, 24, 23, 3 }, ++ [QCOM_RPM_SYS_FABRIC_IOCTL] = { 58, 25, 24, 1 }, ++ [QCOM_RPM_SYS_FABRIC_ARB] = { 59, 26, 25, 30 }, ++ [QCOM_RPM_MM_FABRIC_HALT] = { 89, 27, 26, 2 }, ++ [QCOM_RPM_MM_FABRIC_MODE] = { 91, 28, 27, 3 }, ++ [QCOM_RPM_MM_FABRIC_IOCTL] = { 94, 29, 28, 1 }, ++ [QCOM_RPM_MM_FABRIC_ARB] = { 95, 30, 29, 2 }, ++ [QCOM_RPM_CXO_BUFFERS] = { 209, 33, 31, 1 }, ++ [QCOM_RPM_USB_OTG_SWITCH] = { 210, 34, 32, 1 }, ++ [QCOM_RPM_HDMI_SWITCH] = { 211, 35, 33, 1 }, ++ [QCOM_RPM_DDR_DMM] = { 212, 36, 34, 2 }, ++ [QCOM_RPM_VDDMIN_GPIO] = { 215, 40, 39, 1 }, ++ [QCOM_RPM_SMB208_S1a] = { 216, 41, 90, 2 }, ++ [QCOM_RPM_SMB208_S1b] = { 218, 43, 91, 2 }, ++ [QCOM_RPM_SMB208_S2a] = { 220, 45, 92, 2 }, ++ [QCOM_RPM_SMB208_S2b] = { 222, 47, 93, 2 }, ++}; ++ ++static const struct qcom_rpm_data ipq806x_template = { ++ .version = 3, ++ .resource_table = ipq806x_rpm_resource_table, ++ .n_resources = ARRAY_SIZE(ipq806x_rpm_resource_table), ++}; ++ + static const struct of_device_id qcom_rpm_of_match[] = { + { .compatible = "qcom,rpm-apq8064", .data = &apq8064_template }, + { .compatible = "qcom,rpm-msm8660", .data = &msm8660_template }, + { .compatible = "qcom,rpm-msm8960", .data = &msm8960_template }, ++ { .compatible = "qcom,rpm-ipq8064", .data = &ipq806x_template }, + { } + }; + MODULE_DEVICE_TABLE(of, qcom_rpm_of_match); diff --git a/target/linux/ipq806x/patches-3.18/122-mfd-devicetree-bindings-Add-Qualcomm-RPM-DT-binding.patch b/target/linux/ipq806x/patches-3.18/122-mfd-devicetree-bindings-Add-Qualcomm-RPM-DT-binding.patch new file mode 100644 index 0000000..a65d2c5 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/122-mfd-devicetree-bindings-Add-Qualcomm-RPM-DT-binding.patch @@ -0,0 +1,247 @@ +From aa0c4b815045420ea54d5ae5362f5a0190609d46 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Wed, 26 Nov 2014 13:50:59 -0800 +Subject: [PATCH] mfd: devicetree: bindings: Add Qualcomm RPM DT binding + +Add binding for the Qualcomm Resource Power Manager (RPM) found in 8660, +8960 and 8064 based devices. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Lee Jones +--- + Documentation/devicetree/bindings/mfd/qcom-rpm.txt | 70 ++++++++++ + include/dt-bindings/mfd/qcom-rpm.h | 154 +++++++++++++++++++++ + 2 files changed, 224 insertions(+) + create mode 100644 Documentation/devicetree/bindings/mfd/qcom-rpm.txt + create mode 100644 include/dt-bindings/mfd/qcom-rpm.h + +--- /dev/null ++++ b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt +@@ -0,0 +1,70 @@ ++Qualcomm Resource Power Manager (RPM) ++ ++This driver is used to interface with the Resource Power Manager (RPM) found in ++various Qualcomm platforms. The RPM allows each component in the system to vote ++for state of the system resources, such as clocks, regulators and bus ++frequencies. ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be one of: ++ "qcom,rpm-apq8064" ++ "qcom,rpm-msm8660" ++ "qcom,rpm-msm8960" ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: base address and size of the RPM's message ram ++ ++- interrupts: ++ Usage: required ++ Value type: ++ Definition: three entries specifying the RPM's: ++ 1. acknowledgement interrupt ++ 2. error interrupt ++ 3. wakeup interrupt ++ ++- interrupt-names: ++ Usage: required ++ Value type: ++ Definition: must be the three strings "ack", "err" and "wakeup", in order ++ ++- #address-cells: ++ Usage: required ++ Value type: ++ Definition: must be 1 ++ ++- #size-cells: ++ Usage: required ++ Value type: ++ Definition: must be 0 ++ ++- qcom,ipc: ++ Usage: required ++ Value type: ++ ++ Definition: three entries specifying the outgoing ipc bit used for ++ signaling the RPM: ++ - phandle to a syscon node representing the apcs registers ++ - u32 representing offset to the register within the syscon ++ - u32 representing the ipc bit within the register ++ ++ ++= EXAMPLE ++ ++ #include ++ ++ rpm@108000 { ++ compatible = "qcom,rpm-msm8960"; ++ reg = <0x108000 0x1000>; ++ qcom,ipc = <&apcs 0x8 2>; ++ ++ interrupts = <0 19 0>, <0 21 0>, <0 22 0>; ++ interrupt-names = "ack", "err", "wakeup"; ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ }; ++ +--- /dev/null ++++ b/include/dt-bindings/mfd/qcom-rpm.h +@@ -0,0 +1,154 @@ ++/* ++ * This header provides constants for the Qualcomm RPM bindings. ++ */ ++ ++#ifndef _DT_BINDINGS_MFD_QCOM_RPM_H ++#define _DT_BINDINGS_MFD_QCOM_RPM_H ++ ++/* ++ * Constants use to identify individual resources in the RPM. ++ */ ++#define QCOM_RPM_APPS_FABRIC_ARB 1 ++#define QCOM_RPM_APPS_FABRIC_CLK 2 ++#define QCOM_RPM_APPS_FABRIC_HALT 3 ++#define QCOM_RPM_APPS_FABRIC_IOCTL 4 ++#define QCOM_RPM_APPS_FABRIC_MODE 5 ++#define QCOM_RPM_APPS_L2_CACHE_CTL 6 ++#define QCOM_RPM_CFPB_CLK 7 ++#define QCOM_RPM_CXO_BUFFERS 8 ++#define QCOM_RPM_CXO_CLK 9 ++#define QCOM_RPM_DAYTONA_FABRIC_CLK 10 ++#define QCOM_RPM_DDR_DMM 11 ++#define QCOM_RPM_EBI1_CLK 12 ++#define QCOM_RPM_HDMI_SWITCH 13 ++#define QCOM_RPM_MMFPB_CLK 14 ++#define QCOM_RPM_MM_FABRIC_ARB 15 ++#define QCOM_RPM_MM_FABRIC_CLK 16 ++#define QCOM_RPM_MM_FABRIC_HALT 17 ++#define QCOM_RPM_MM_FABRIC_IOCTL 18 ++#define QCOM_RPM_MM_FABRIC_MODE 19 ++#define QCOM_RPM_PLL_4 20 ++#define QCOM_RPM_PM8058_LDO0 21 ++#define QCOM_RPM_PM8058_LDO1 22 ++#define QCOM_RPM_PM8058_LDO2 23 ++#define QCOM_RPM_PM8058_LDO3 24 ++#define QCOM_RPM_PM8058_LDO4 25 ++#define QCOM_RPM_PM8058_LDO5 26 ++#define QCOM_RPM_PM8058_LDO6 27 ++#define QCOM_RPM_PM8058_LDO7 28 ++#define QCOM_RPM_PM8058_LDO8 29 ++#define QCOM_RPM_PM8058_LDO9 30 ++#define QCOM_RPM_PM8058_LDO10 31 ++#define QCOM_RPM_PM8058_LDO11 32 ++#define QCOM_RPM_PM8058_LDO12 33 ++#define QCOM_RPM_PM8058_LDO13 34 ++#define QCOM_RPM_PM8058_LDO14 35 ++#define QCOM_RPM_PM8058_LDO15 36 ++#define QCOM_RPM_PM8058_LDO16 37 ++#define QCOM_RPM_PM8058_LDO17 38 ++#define QCOM_RPM_PM8058_LDO18 39 ++#define QCOM_RPM_PM8058_LDO19 40 ++#define QCOM_RPM_PM8058_LDO20 41 ++#define QCOM_RPM_PM8058_LDO21 42 ++#define QCOM_RPM_PM8058_LDO22 43 ++#define QCOM_RPM_PM8058_LDO23 44 ++#define QCOM_RPM_PM8058_LDO24 45 ++#define QCOM_RPM_PM8058_LDO25 46 ++#define QCOM_RPM_PM8058_LVS0 47 ++#define QCOM_RPM_PM8058_LVS1 48 ++#define QCOM_RPM_PM8058_NCP 49 ++#define QCOM_RPM_PM8058_SMPS0 50 ++#define QCOM_RPM_PM8058_SMPS1 51 ++#define QCOM_RPM_PM8058_SMPS2 52 ++#define QCOM_RPM_PM8058_SMPS3 53 ++#define QCOM_RPM_PM8058_SMPS4 54 ++#define QCOM_RPM_PM8821_LDO1 55 ++#define QCOM_RPM_PM8821_SMPS1 56 ++#define QCOM_RPM_PM8821_SMPS2 57 ++#define QCOM_RPM_PM8901_LDO0 58 ++#define QCOM_RPM_PM8901_LDO1 59 ++#define QCOM_RPM_PM8901_LDO2 60 ++#define QCOM_RPM_PM8901_LDO3 61 ++#define QCOM_RPM_PM8901_LDO4 62 ++#define QCOM_RPM_PM8901_LDO5 63 ++#define QCOM_RPM_PM8901_LDO6 64 ++#define QCOM_RPM_PM8901_LVS0 65 ++#define QCOM_RPM_PM8901_LVS1 66 ++#define QCOM_RPM_PM8901_LVS2 67 ++#define QCOM_RPM_PM8901_LVS3 68 ++#define QCOM_RPM_PM8901_MVS 69 ++#define QCOM_RPM_PM8901_SMPS0 70 ++#define QCOM_RPM_PM8901_SMPS1 71 ++#define QCOM_RPM_PM8901_SMPS2 72 ++#define QCOM_RPM_PM8901_SMPS3 73 ++#define QCOM_RPM_PM8901_SMPS4 74 ++#define QCOM_RPM_PM8921_CLK1 75 ++#define QCOM_RPM_PM8921_CLK2 76 ++#define QCOM_RPM_PM8921_LDO1 77 ++#define QCOM_RPM_PM8921_LDO2 78 ++#define QCOM_RPM_PM8921_LDO3 79 ++#define QCOM_RPM_PM8921_LDO4 80 ++#define QCOM_RPM_PM8921_LDO5 81 ++#define QCOM_RPM_PM8921_LDO6 82 ++#define QCOM_RPM_PM8921_LDO7 83 ++#define QCOM_RPM_PM8921_LDO8 84 ++#define QCOM_RPM_PM8921_LDO9 85 ++#define QCOM_RPM_PM8921_LDO10 86 ++#define QCOM_RPM_PM8921_LDO11 87 ++#define QCOM_RPM_PM8921_LDO12 88 ++#define QCOM_RPM_PM8921_LDO13 89 ++#define QCOM_RPM_PM8921_LDO14 90 ++#define QCOM_RPM_PM8921_LDO15 91 ++#define QCOM_RPM_PM8921_LDO16 92 ++#define QCOM_RPM_PM8921_LDO17 93 ++#define QCOM_RPM_PM8921_LDO18 94 ++#define QCOM_RPM_PM8921_LDO19 95 ++#define QCOM_RPM_PM8921_LDO20 96 ++#define QCOM_RPM_PM8921_LDO21 97 ++#define QCOM_RPM_PM8921_LDO22 98 ++#define QCOM_RPM_PM8921_LDO23 99 ++#define QCOM_RPM_PM8921_LDO24 100 ++#define QCOM_RPM_PM8921_LDO25 101 ++#define QCOM_RPM_PM8921_LDO26 102 ++#define QCOM_RPM_PM8921_LDO27 103 ++#define QCOM_RPM_PM8921_LDO28 104 ++#define QCOM_RPM_PM8921_LDO29 105 ++#define QCOM_RPM_PM8921_LVS1 106 ++#define QCOM_RPM_PM8921_LVS2 107 ++#define QCOM_RPM_PM8921_LVS3 108 ++#define QCOM_RPM_PM8921_LVS4 109 ++#define QCOM_RPM_PM8921_LVS5 110 ++#define QCOM_RPM_PM8921_LVS6 111 ++#define QCOM_RPM_PM8921_LVS7 112 ++#define QCOM_RPM_PM8921_MVS 113 ++#define QCOM_RPM_PM8921_NCP 114 ++#define QCOM_RPM_PM8921_SMPS1 115 ++#define QCOM_RPM_PM8921_SMPS2 116 ++#define QCOM_RPM_PM8921_SMPS3 117 ++#define QCOM_RPM_PM8921_SMPS4 118 ++#define QCOM_RPM_PM8921_SMPS5 119 ++#define QCOM_RPM_PM8921_SMPS6 120 ++#define QCOM_RPM_PM8921_SMPS7 121 ++#define QCOM_RPM_PM8921_SMPS8 122 ++#define QCOM_RPM_PXO_CLK 123 ++#define QCOM_RPM_QDSS_CLK 124 ++#define QCOM_RPM_SFPB_CLK 125 ++#define QCOM_RPM_SMI_CLK 126 ++#define QCOM_RPM_SYS_FABRIC_ARB 127 ++#define QCOM_RPM_SYS_FABRIC_CLK 128 ++#define QCOM_RPM_SYS_FABRIC_HALT 129 ++#define QCOM_RPM_SYS_FABRIC_IOCTL 130 ++#define QCOM_RPM_SYS_FABRIC_MODE 131 ++#define QCOM_RPM_USB_OTG_SWITCH 132 ++#define QCOM_RPM_VDDMIN_GPIO 133 ++ ++/* ++ * Constants used to select force mode for regulators. ++ */ ++#define QCOM_RPM_FORCE_MODE_NONE 0 ++#define QCOM_RPM_FORCE_MODE_LPM 1 ++#define QCOM_RPM_FORCE_MODE_HPM 2 ++#define QCOM_RPM_FORCE_MODE_AUTO 3 ++#define QCOM_RPM_FORCE_MODE_BYPASS 4 ++ ++#endif diff --git a/target/linux/ipq806x/patches-3.18/123-mfd-devicetree-qcom_rpm-Document-IPQ8064-resources.patch b/target/linux/ipq806x/patches-3.18/123-mfd-devicetree-qcom_rpm-Document-IPQ8064-resources.patch new file mode 100644 index 0000000..c8a9f3f --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/123-mfd-devicetree-qcom_rpm-Document-IPQ8064-resources.patch @@ -0,0 +1,42 @@ +From 30bc3aa5c4ed3072bdff7d915772df1b91307ed4 Mon Sep 17 00:00:00 2001 +From: Josh Cartwright +Date: Thu, 26 Mar 2015 11:29:25 -0700 +Subject: [PATCH] mfd: devicetree: qcom_rpm: Document IPQ8064 resources + +The IPQ8064 SoC has several RPM-controlled resources, an NSS fabrick +clock and four regulator resources. Provide definitions for them. + +Signed-off-by: Josh Cartwright +[sboyd@codeaurora.org: Drop regulator part of binding] +Signed-off-by: Stephen Boyd +Signed-off-by: Lee Jones +--- + Documentation/devicetree/bindings/mfd/qcom-rpm.txt | 1 + + include/dt-bindings/mfd/qcom-rpm.h | 6 ++++++ + 2 files changed, 7 insertions(+) + +--- a/Documentation/devicetree/bindings/mfd/qcom-rpm.txt ++++ b/Documentation/devicetree/bindings/mfd/qcom-rpm.txt +@@ -12,6 +12,7 @@ frequencies. + "qcom,rpm-apq8064" + "qcom,rpm-msm8660" + "qcom,rpm-msm8960" ++ "qcom,rpm-ipq8064" + + - reg: + Usage: required +--- a/include/dt-bindings/mfd/qcom-rpm.h ++++ b/include/dt-bindings/mfd/qcom-rpm.h +@@ -141,6 +141,12 @@ + #define QCOM_RPM_SYS_FABRIC_MODE 131 + #define QCOM_RPM_USB_OTG_SWITCH 132 + #define QCOM_RPM_VDDMIN_GPIO 133 ++#define QCOM_RPM_NSS_FABRIC_0_CLK 134 ++#define QCOM_RPM_NSS_FABRIC_1_CLK 135 ++#define QCOM_RPM_SMB208_S1a 136 ++#define QCOM_RPM_SMB208_S1b 137 ++#define QCOM_RPM_SMB208_S2a 138 ++#define QCOM_RPM_SMB208_S2b 139 + + /* + * Constants used to select force mode for regulators. diff --git a/target/linux/ipq806x/patches-3.18/124-regulator-rpm-add-support-for-RPM-controller-SMB208.patch b/target/linux/ipq806x/patches-3.18/124-regulator-rpm-add-support-for-RPM-controller-SMB208.patch new file mode 100644 index 0000000..e4f094c --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/124-regulator-rpm-add-support-for-RPM-controller-SMB208.patch @@ -0,0 +1,58 @@ +From 0f5bb5b5de3b18877373f746bdb85d8ea0efeedf Mon Sep 17 00:00:00 2001 +From: Josh Cartwright +Date: Thu, 20 Nov 2014 13:41:25 -0600 +Subject: [PATCH] regulator: rpm: add support for RPM-controller SMB208 + +The IPQ8064 reference boards make use of SMB208 regulators which are +controlled by RPM. Implement support for these regulators in the RPM +regulator driver. + +Signed-off-by: Josh Cartwright +Acked-by: Bjorn Andersson +Signed-off-by: Mark Brown +--- + drivers/regulator/qcom_rpm-regulator.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +--- a/drivers/regulator/qcom_rpm-regulator.c ++++ b/drivers/regulator/qcom_rpm-regulator.c +@@ -183,6 +183,13 @@ static const struct regulator_linear_ran + REGULATOR_LINEAR_RANGE(1500000, 64, 100, 50000), + }; + ++static const struct regulator_linear_range smb208_ranges[] = { ++ REGULATOR_LINEAR_RANGE( 375000, 0, 29, 12500), ++ REGULATOR_LINEAR_RANGE( 750000, 30, 89, 12500), ++ REGULATOR_LINEAR_RANGE(1500000, 90, 153, 25000), ++ REGULATOR_LINEAR_RANGE(3100000, 154, 234, 25000), ++}; ++ + static const struct regulator_linear_range ncp_ranges[] = { + REGULATOR_LINEAR_RANGE(1500000, 0, 31, 50000), + }; +@@ -559,6 +566,16 @@ static const struct qcom_rpm_reg pm8921_ + .parts = &rpm8960_switch_parts, + }; + ++static const struct qcom_rpm_reg smb208_smps = { ++ .desc.linear_ranges = smb208_ranges, ++ .desc.n_linear_ranges = ARRAY_SIZE(smb208_ranges), ++ .desc.n_voltages = 235, ++ .desc.ops = &uV_ops, ++ .parts = &rpm8960_smps_parts, ++ .supports_force_mode_auto = false, ++ .supports_force_mode_bypass = false, ++}; ++ + static const struct of_device_id rpm_of_match[] = { + { .compatible = "qcom,rpm-pm8058-pldo", .data = &pm8058_pldo }, + { .compatible = "qcom,rpm-pm8058-nldo", .data = &pm8058_nldo }, +@@ -578,6 +595,8 @@ static const struct of_device_id rpm_of_ + { .compatible = "qcom,rpm-pm8921-ftsmps", .data = &pm8921_ftsmps }, + { .compatible = "qcom,rpm-pm8921-ncp", .data = &pm8921_ncp }, + { .compatible = "qcom,rpm-pm8921-switch", .data = &pm8921_switch }, ++ ++ { .compatible = "qcom,rpm-smb208", .data = &smb208_smps }, + { } + }; + MODULE_DEVICE_TABLE(of, rpm_of_match); diff --git a/target/linux/ipq806x/patches-3.18/125-regulator-qcom-rpm-Add-missing-state-flag-in-call-to.patch b/target/linux/ipq806x/patches-3.18/125-regulator-qcom-rpm-Add-missing-state-flag-in-call-to.patch new file mode 100644 index 0000000..48921a8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/125-regulator-qcom-rpm-Add-missing-state-flag-in-call-to.patch @@ -0,0 +1,25 @@ +From 803926825fa4db007437f76654e3e63bafb9b906 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Wed, 26 Nov 2014 13:51:01 -0800 +Subject: [PATCH] regulator: qcom-rpm: Add missing state flag in call to RPM + +This adds the missing state parameter to the call down to the RPM. This +is currently hard coded to the active state, as that's all we're +supporting at this moment. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Lee Jones +--- + drivers/regulator/qcom_rpm-regulator.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/drivers/regulator/qcom_rpm-regulator.c ++++ b/drivers/regulator/qcom_rpm-regulator.c +@@ -205,6 +205,7 @@ static int rpm_reg_write(struct qcom_rpm + vreg->val[req->word] |= value << req->shift; + + return qcom_rpm_write(vreg->rpm, ++ QCOM_RPM_ACTIVE_STATE, + vreg->resource, + vreg->val, + vreg->parts->request_len); diff --git a/target/linux/ipq806x/patches-3.18/126-add-rpm-to-ipq8064-dts.patch b/target/linux/ipq806x/patches-3.18/126-add-rpm-to-ipq8064-dts.patch new file mode 100644 index 0000000..7e4c5cb --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/126-add-rpm-to-ipq8064-dts.patch @@ -0,0 +1,87 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -2,6 +2,7 @@ + + #include "skeleton.dtsi" + #include ++#include + #include + #include + #include +@@ -76,6 +77,63 @@ + ranges; + compatible = "simple-bus"; + ++ rpm@108000 { ++ compatible = "qcom,rpm-ipq8064"; ++ reg = <0x108000 0x1000>; ++ qcom,ipc = <&l2cc 0x8 2>; ++ ++ interrupts = <0 19 0>, ++ <0 21 0>, ++ <0 22 0>; ++ interrupt-names = "ack", ++ "err", ++ "wakeup"; ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ smb208_s1a: smb208-s1a { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = <1050000>; ++ regulator-max-microvolt = <1150000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ ++ }; ++ ++ smb208_s1b: smb208-s1b { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = <1050000>; ++ regulator-max-microvolt = <1150000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ ++ smb208_s2a: smb208-s2a { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = < 800000>; ++ regulator-max-microvolt = <1250000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ ++ smb208_s2b: smb208-s2b { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = < 800000>; ++ regulator-max-microvolt = <1250000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ }; ++ + qcom_pinmux: pinmux@800000 { + compatible = "qcom,ipq8064-pinctrl"; + reg = <0x800000 0x4000>; +@@ -120,6 +178,12 @@ + reg = <0x02098000 0x1000>, <0x02008000 0x1000>; + }; + ++ l2cc: clock-controller@2011000 { ++ compatible = "qcom,kpss-gcc", "syscon"; ++ reg = <0x2011000 0x1000>; ++ clock-output-names = "acpu_l2_aux"; ++ }; ++ + saw0: regulator@2089000 { + compatible = "qcom,saw2"; + reg = <0x02089000 0x1000>, <0x02009000 0x1000>; diff --git a/target/linux/ipq806x/patches-3.18/130-clk_mux-Fix-set_parent-doing-the-wrong-thing-when-IN.patch b/target/linux/ipq806x/patches-3.18/130-clk_mux-Fix-set_parent-doing-the-wrong-thing-when-IN.patch new file mode 100644 index 0000000..29f74b7 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/130-clk_mux-Fix-set_parent-doing-the-wrong-thing-when-IN.patch @@ -0,0 +1,55 @@ +From 6793b3cd5da817c4be218bd8632f07cf4d2b0d26 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Wed, 19 Nov 2014 14:48:59 +0100 +Subject: [PATCH] clk_mux: Fix set_parent doing the wrong thing when INDEX_BIT + && index >= 3 + +If CLK_MUX_INDEX_BIT is set, then each bit turns on / off a single parent, +so theoretically multiple parents could be enabled at the same time, but in +practice only one bit should ever be 1. So to select parent 0, set +the register (*) to 0x01, to select parent 1 set it 0x02, parent 2, 0x04, +parent 3, 0x08, etc. + +But the current code does: + + if (mux->flags & CLK_MUX_INDEX_BIT) + index = (1 << ffs(index)); + +Which means that: + +For an input index of 0, ffs returns 0, so we set the register +to 0x01, ok. + +For an input index of 1, ffs returns 1, so we set the register +to 0x02, ok. + +For an input index of 2, ffs returns 2, so we set the register +to 0x04, ok. + +For an input index of 3, ffs returns 1, so we set the register +to 0x02, not good! + +The code should simply be: + + if (mux->flags & CLK_MUX_INDEX_BIT) + index = 1 << index; + +Which always does the right thing, this commit fixes this. + +Signed-off-by: Hans de Goede +Signed-off-by: Michael Turquette +--- + drivers/clk/clk-mux.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/clk/clk-mux.c ++++ b/drivers/clk/clk-mux.c +@@ -77,7 +77,7 @@ static int clk_mux_set_parent(struct clk + + else { + if (mux->flags & CLK_MUX_INDEX_BIT) +- index = (1 << ffs(index)); ++ index = 1 << index; + + if (mux->flags & CLK_MUX_INDEX_ONE) + index++; diff --git a/target/linux/ipq806x/patches-3.18/131-clk-Add-__clk_mux_determine_rate_closest.patch b/target/linux/ipq806x/patches-3.18/131-clk-Add-__clk_mux_determine_rate_closest.patch new file mode 100644 index 0000000..18972f3 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/131-clk-Add-__clk_mux_determine_rate_closest.patch @@ -0,0 +1,120 @@ +From 15a02c1f6dd7c2bb150c61d00ffb33f584ff2288 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd +Date: Mon, 19 Jan 2015 18:05:28 -0800 +Subject: [PATCH] clk: Add __clk_mux_determine_rate_closest + +Some clock drivers want to find the closest rate on the input of +a mux instead of a rate that's less than or equal to the desired +rate. Add a generic mux function to support this. + +Signed-off-by: Stephen Boyd +Tested-by: Kenneth Westfield +Signed-off-by: Michael Turquette +--- + drivers/clk/clk.c | 47 +++++++++++++++++++++++++++++++++++--------- + include/linux/clk-provider.h | 8 +++++++- + 2 files changed, 45 insertions(+), 10 deletions(-) + +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -695,14 +695,20 @@ struct clk *__clk_lookup(const char *nam + return NULL; + } + +-/* +- * Helper for finding best parent to provide a given frequency. This can be used +- * directly as a determine_rate callback (e.g. for a mux), or from a more +- * complex clock that may combine a mux with other operations. +- */ +-long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate, +- unsigned long *best_parent_rate, +- struct clk **best_parent_p) ++static bool mux_is_better_rate(unsigned long rate, unsigned long now, ++ unsigned long best, unsigned long flags) ++{ ++ if (flags & CLK_MUX_ROUND_CLOSEST) ++ return abs(now - rate) < abs(best - rate); ++ ++ return now <= rate && now > best; ++} ++ ++static long ++clk_mux_determine_rate_flags(struct clk_hw *hw, unsigned long rate, ++ unsigned long *best_parent_rate, ++ struct clk **best_parent_p, ++ unsigned long flags) + { + struct clk *clk = hw->clk, *parent, *best_parent = NULL; + int i, num_parents; +@@ -730,7 +736,7 @@ long __clk_mux_determine_rate(struct clk + parent_rate = __clk_round_rate(parent, rate); + else + parent_rate = __clk_get_rate(parent); +- if (parent_rate <= rate && parent_rate > best) { ++ if (mux_is_better_rate(rate, parent_rate, best, flags)) { + best_parent = parent; + best = parent_rate; + } +@@ -743,8 +749,31 @@ out: + + return best; + } ++ ++/* ++ * Helper for finding best parent to provide a given frequency. This can be used ++ * directly as a determine_rate callback (e.g. for a mux), or from a more ++ * complex clock that may combine a mux with other operations. ++ */ ++long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *best_parent_rate, ++ struct clk **best_parent_p) ++{ ++ return clk_mux_determine_rate_flags(hw, rate, best_parent_rate, ++ best_parent_p, 0); ++} + EXPORT_SYMBOL_GPL(__clk_mux_determine_rate); + ++long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate, ++ unsigned long *best_parent_rate, ++ struct clk **best_parent_p) ++{ ++ return clk_mux_determine_rate_flags(hw, rate, best_parent_rate, ++ best_parent_p, ++ CLK_MUX_ROUND_CLOSEST); ++} ++EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest); ++ + /*** clk api ***/ + + void __clk_unprepare(struct clk *clk) +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -382,6 +382,8 @@ struct clk *clk_register_divider_table(s + * register, and mask of mux bits are in higher 16-bit of this register. + * While setting the mux bits, higher 16-bit should also be updated to + * indicate changing mux bits. ++ * CLK_MUX_ROUND_CLOSEST - Use the parent rate that is closest to the desired ++ * frequency. + */ + struct clk_mux { + struct clk_hw hw; +@@ -396,7 +398,8 @@ struct clk_mux { + #define CLK_MUX_INDEX_ONE BIT(0) + #define CLK_MUX_INDEX_BIT BIT(1) + #define CLK_MUX_HIWORD_MASK BIT(2) +-#define CLK_MUX_READ_ONLY BIT(3) /* mux setting cannot be changed */ ++#define CLK_MUX_READ_ONLY BIT(3) /* mux can't be changed */ ++#define CLK_MUX_ROUND_CLOSEST BIT(4) + + extern const struct clk_ops clk_mux_ops; + extern const struct clk_ops clk_mux_ro_ops; +@@ -554,6 +557,9 @@ struct clk *__clk_lookup(const char *nam + long __clk_mux_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate, + struct clk **best_parent_p); ++long __clk_mux_determine_rate_closest(struct clk_hw *hw, unsigned long rate, ++ unsigned long *best_parent_rate, ++ struct clk **best_parent_p); + + /* + * FIXME clock api without lock protection diff --git a/target/linux/ipq806x/patches-3.18/132-clk-Add-clk_unregister_-divider-gate-mux-to-close-me.patch b/target/linux/ipq806x/patches-3.18/132-clk-Add-clk_unregister_-divider-gate-mux-to-close-me.patch new file mode 100644 index 0000000..790f25d --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/132-clk-Add-clk_unregister_-divider-gate-mux-to-close-me.patch @@ -0,0 +1,115 @@ +From 4e3c021fb995bcbb5d1f814d00584cb80eb904a8 Mon Sep 17 00:00:00 2001 +From: Krzysztof Kozlowski +Date: Mon, 5 Jan 2015 10:52:40 +0100 +Subject: [PATCH] clk: Add clk_unregister_{divider, gate, mux} to close memory + leak + +The common clk_register_{divider,gate,mux} functions allocated memory +for internal data which wasn't freed anywhere. Drivers using these +helpers could only unregister clocks but the memory would still leak. + +Add corresponding unregister functions which will release all resources. + +Signed-off-by: Krzysztof Kozlowski +Reviewed-by: Stephen Boyd +Signed-off-by: Michael Turquette +--- + drivers/clk/clk-divider.c | 16 ++++++++++++++++ + drivers/clk/clk-gate.c | 16 ++++++++++++++++ + drivers/clk/clk-mux.c | 16 ++++++++++++++++ + include/linux/clk-provider.h | 4 ++++ + 4 files changed, 52 insertions(+) + +--- a/drivers/clk/clk-divider.c ++++ b/drivers/clk/clk-divider.c +@@ -461,3 +461,19 @@ struct clk *clk_register_divider_table(s + width, clk_divider_flags, table, lock); + } + EXPORT_SYMBOL_GPL(clk_register_divider_table); ++ ++void clk_unregister_divider(struct clk *clk) ++{ ++ struct clk_divider *div; ++ struct clk_hw *hw; ++ ++ hw = __clk_get_hw(clk); ++ if (!hw) ++ return; ++ ++ div = to_clk_divider(hw); ++ ++ clk_unregister(clk); ++ kfree(div); ++} ++EXPORT_SYMBOL_GPL(clk_unregister_divider); +--- a/drivers/clk/clk-gate.c ++++ b/drivers/clk/clk-gate.c +@@ -162,3 +162,19 @@ struct clk *clk_register_gate(struct dev + return clk; + } + EXPORT_SYMBOL_GPL(clk_register_gate); ++ ++void clk_unregister_gate(struct clk *clk) ++{ ++ struct clk_gate *gate; ++ struct clk_hw *hw; ++ ++ hw = __clk_get_hw(clk); ++ if (!hw) ++ return; ++ ++ gate = to_clk_gate(hw); ++ ++ clk_unregister(clk); ++ kfree(gate); ++} ++EXPORT_SYMBOL_GPL(clk_unregister_gate); +--- a/drivers/clk/clk-mux.c ++++ b/drivers/clk/clk-mux.c +@@ -177,3 +177,19 @@ struct clk *clk_register_mux(struct devi + NULL, lock); + } + EXPORT_SYMBOL_GPL(clk_register_mux); ++ ++void clk_unregister_mux(struct clk *clk) ++{ ++ struct clk_mux *mux; ++ struct clk_hw *hw; ++ ++ hw = __clk_get_hw(clk); ++ if (!hw) ++ return; ++ ++ mux = to_clk_mux(hw); ++ ++ clk_unregister(clk); ++ kfree(mux); ++} ++EXPORT_SYMBOL_GPL(clk_unregister_mux); +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -294,6 +294,7 @@ struct clk *clk_register_gate(struct dev + const char *parent_name, unsigned long flags, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock); ++void clk_unregister_gate(struct clk *clk); + + struct clk_div_table { + unsigned int val; +@@ -361,6 +362,7 @@ struct clk *clk_register_divider_table(s + void __iomem *reg, u8 shift, u8 width, + u8 clk_divider_flags, const struct clk_div_table *table, + spinlock_t *lock); ++void clk_unregister_divider(struct clk *clk); + + /** + * struct clk_mux - multiplexer clock +@@ -414,6 +416,8 @@ struct clk *clk_register_mux_table(struc + void __iomem *reg, u8 shift, u32 mask, + u8 clk_mux_flags, u32 *table, spinlock_t *lock); + ++void clk_unregister_mux(struct clk *clk); ++ + void of_fixed_factor_clk_setup(struct device_node *node); + + /** diff --git a/target/linux/ipq806x/patches-3.18/133-ARM-Add-Krait-L2-register-accessor-functions.patch b/target/linux/ipq806x/patches-3.18/133-ARM-Add-Krait-L2-register-accessor-functions.patch new file mode 100644 index 0000000..36a92c8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/133-ARM-Add-Krait-L2-register-accessor-functions.patch @@ -0,0 +1,144 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,01/13] ARM: Add Krait L2 register accessor functions +From: Stephen Boyd +X-Patchwork-Id: 6063051 +Message-Id: <1426920332-9340-2-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , + Mark Rutland , Russell King , + Courtney Cavin +Date: Fri, 20 Mar 2015 23:45:20 -0700 + +Krait CPUs have a handful of L2 cache controller registers that +live behind a cp15 based indirection register. First you program +the indirection register (l2cpselr) to point the L2 'window' +register (l2cpdr) at what you want to read/write. Then you +read/write the 'window' register to do what you want. The +l2cpselr register is not banked per-cpu so we must lock around +accesses to it to prevent other CPUs from re-pointing l2cpdr +underneath us. + +Cc: Mark Rutland +Cc: Russell King +Cc: Courtney Cavin +Signed-off-by: Stephen Boyd + +--- +arch/arm/common/Kconfig | 3 ++ + arch/arm/common/Makefile | 1 + + arch/arm/common/krait-l2-accessors.c | 58 +++++++++++++++++++++++++++++++ + arch/arm/include/asm/krait-l2-accessors.h | 20 +++++++++++ + 4 files changed, 82 insertions(+) + create mode 100644 arch/arm/common/krait-l2-accessors.c + create mode 100644 arch/arm/include/asm/krait-l2-accessors.h + +--- a/arch/arm/common/Kconfig ++++ b/arch/arm/common/Kconfig +@@ -9,6 +9,9 @@ config DMABOUNCE + bool + select ZONE_DMA + ++config KRAIT_L2_ACCESSORS ++ bool ++ + config SHARP_LOCOMO + bool + +--- a/arch/arm/common/Makefile ++++ b/arch/arm/common/Makefile +@@ -7,6 +7,7 @@ obj-y += firmware.o + obj-$(CONFIG_ICST) += icst.o + obj-$(CONFIG_SA1111) += sa1111.o + obj-$(CONFIG_DMABOUNCE) += dmabounce.o ++obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o + obj-$(CONFIG_SHARP_LOCOMO) += locomo.o + obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o + obj-$(CONFIG_SHARP_SCOOP) += scoop.o +--- /dev/null ++++ b/arch/arm/common/krait-l2-accessors.c +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++ ++static DEFINE_RAW_SPINLOCK(krait_l2_lock); ++ ++void krait_set_l2_indirect_reg(u32 addr, u32 val) ++{ ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&krait_l2_lock, flags); ++ /* ++ * Select the L2 window by poking l2cpselr, then write to the window ++ * via l2cpdr. ++ */ ++ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); ++ isb(); ++ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val)); ++ isb(); ++ ++ raw_spin_unlock_irqrestore(&krait_l2_lock, flags); ++} ++EXPORT_SYMBOL(krait_set_l2_indirect_reg); ++ ++u32 krait_get_l2_indirect_reg(u32 addr) ++{ ++ u32 val; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&krait_l2_lock, flags); ++ /* ++ * Select the L2 window by poking l2cpselr, then read from the window ++ * via l2cpdr. ++ */ ++ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); ++ isb(); ++ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val)); ++ ++ raw_spin_unlock_irqrestore(&krait_l2_lock, flags); ++ ++ return val; ++} ++EXPORT_SYMBOL(krait_get_l2_indirect_reg); +--- /dev/null ++++ b/arch/arm/include/asm/krait-l2-accessors.h +@@ -0,0 +1,20 @@ ++/* ++ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __ASMARM_KRAIT_L2_ACCESSORS_H ++#define __ASMARM_KRAIT_L2_ACCESSORS_H ++ ++extern void krait_set_l2_indirect_reg(u32 addr, u32 val); ++extern u32 krait_get_l2_indirect_reg(u32 addr); ++ ++#endif diff --git a/target/linux/ipq806x/patches-3.18/134-clk-mux-Split-out-register-accessors-for-reuse.patch b/target/linux/ipq806x/patches-3.18/134-clk-mux-Split-out-register-accessors-for-reuse.patch new file mode 100644 index 0000000..50022e6 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/134-clk-mux-Split-out-register-accessors-for-reuse.patch @@ -0,0 +1,192 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,02/13] clk: mux: Split out register accessors for reuse +From: Stephen Boyd +X-Patchwork-Id: 6063111 +Message-Id: <1426920332-9340-3-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:21 -0700 + +We want to reuse the logic in clk-mux.c for other clock drivers +that don't use readl as register accessors. Fortunately, there +really isn't much to the mux code besides the table indirection +and quirk flags if you assume any bit shifting and masking has +been done already. Pull that logic out into reusable functions +that operate on an optional table and some flags so that other +drivers can use the same logic. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/clk-mux.c | 76 +++++++++++++++++++++++++++----------------- + include/linux/clk-provider.h | 9 ++++-- + 2 files changed, 54 insertions(+), 31 deletions(-) + +--- a/drivers/clk/clk-mux.c ++++ b/drivers/clk/clk-mux.c +@@ -29,35 +29,24 @@ + + #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) + +-static u8 clk_mux_get_parent(struct clk_hw *hw) ++unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val, ++ unsigned int *table, unsigned long flags) + { +- struct clk_mux *mux = to_clk_mux(hw); + int num_parents = __clk_get_num_parents(hw->clk); +- u32 val; + +- /* +- * FIXME need a mux-specific flag to determine if val is bitwise or numeric +- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 +- * to 0x7 (index starts at one) +- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so +- * val = 0x4 really means "bit 2, index starts at bit 0" +- */ +- val = clk_readl(mux->reg) >> mux->shift; +- val &= mux->mask; +- +- if (mux->table) { ++ if (table) { + int i; + + for (i = 0; i < num_parents; i++) +- if (mux->table[i] == val) ++ if (table[i] == val) + return i; + return -EINVAL; + } + +- if (val && (mux->flags & CLK_MUX_INDEX_BIT)) ++ if (val && (flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + +- if (val && (mux->flags & CLK_MUX_INDEX_ONE)) ++ if (val && (flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= num_parents) +@@ -65,24 +54,53 @@ static u8 clk_mux_get_parent(struct clk_ + + return val; + } ++EXPORT_SYMBOL_GPL(clk_mux_get_parent); + +-static int clk_mux_set_parent(struct clk_hw *hw, u8 index) ++static u8 _clk_mux_get_parent(struct clk_hw *hw) + { + struct clk_mux *mux = to_clk_mux(hw); + u32 val; +- unsigned long flags = 0; + +- if (mux->table) +- index = mux->table[index]; ++ /* ++ * FIXME need a mux-specific flag to determine if val is bitwise or numeric ++ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 ++ * to 0x7 (index starts at one) ++ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so ++ * val = 0x4 really means "bit 2, index starts at bit 0" ++ */ ++ val = clk_readl(mux->reg) >> mux->shift; ++ val &= mux->mask; ++ ++ return clk_mux_get_parent(hw, val, mux->table, mux->flags); ++} + +- else { +- if (mux->flags & CLK_MUX_INDEX_BIT) +- index = 1 << index; ++unsigned int clk_mux_reindex(u8 index, unsigned int *table, ++ unsigned long flags) ++{ ++ unsigned int val = index; + +- if (mux->flags & CLK_MUX_INDEX_ONE) +- index++; ++ if (table) { ++ val = table[val]; ++ } else { ++ if (flags & CLK_MUX_INDEX_BIT) ++ val = 1 << index; ++ ++ if (flags & CLK_MUX_INDEX_ONE) ++ val++; + } + ++ return val; ++} ++EXPORT_SYMBOL_GPL(clk_mux_reindex); ++ ++static int clk_mux_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct clk_mux *mux = to_clk_mux(hw); ++ u32 val; ++ unsigned long flags = 0; ++ ++ index = clk_mux_reindex(index, mux->table, mux->flags); ++ + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + +@@ -102,21 +120,21 @@ static int clk_mux_set_parent(struct clk + } + + const struct clk_ops clk_mux_ops = { +- .get_parent = clk_mux_get_parent, ++ .get_parent = _clk_mux_get_parent, + .set_parent = clk_mux_set_parent, + .determine_rate = __clk_mux_determine_rate, + }; + EXPORT_SYMBOL_GPL(clk_mux_ops); + + const struct clk_ops clk_mux_ro_ops = { +- .get_parent = clk_mux_get_parent, ++ .get_parent = _clk_mux_get_parent, + }; + EXPORT_SYMBOL_GPL(clk_mux_ro_ops); + + struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, +- u8 clk_mux_flags, u32 *table, spinlock_t *lock) ++ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock) + { + struct clk_mux *mux; + struct clk *clk; +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -390,7 +390,7 @@ void clk_unregister_divider(struct clk * + struct clk_mux { + struct clk_hw hw; + void __iomem *reg; +- u32 *table; ++ unsigned int *table; + u32 mask; + u8 shift; + u8 flags; +@@ -406,6 +406,11 @@ struct clk_mux { + extern const struct clk_ops clk_mux_ops; + extern const struct clk_ops clk_mux_ro_ops; + ++unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val, ++ unsigned int *table, unsigned long flags); ++unsigned int clk_mux_reindex(u8 index, unsigned int *table, ++ unsigned long flags); ++ + struct clk *clk_register_mux(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u8 width, +@@ -414,7 +419,7 @@ struct clk *clk_register_mux(struct devi + struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, +- u8 clk_mux_flags, u32 *table, spinlock_t *lock); ++ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock); + + void clk_unregister_mux(struct clk *clk); + diff --git a/target/linux/ipq806x/patches-3.18/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch b/target/linux/ipq806x/patches-3.18/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch new file mode 100644 index 0000000..02d96ad --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch @@ -0,0 +1,129 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3, 03/13] clk: Avoid sending high rates to downstream clocks during + set_rate +From: Stephen Boyd +X-Patchwork-Id: 6063271 +Message-Id: <1426920332-9340-4-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:22 -0700 + +If a clock is on and we call clk_set_rate() on it we may get into +a situation where the clock temporarily increases in rate +dramatically while we walk the tree and call .set_rate() ops. For +example, consider a case where a PLL feeds into a divider. +Initially the divider is set to divide by 1 and the PLL is +running fairly slow (100MHz). The downstream consumer of the +divider output can only handle rates =< 400 MHz, but the divider +can only choose between divisors of 1 and 4. + + +-----+ +----------------+ + | PLL |-->| div 1 or div 4 |---> consumer device + +-----+ +----------------+ + +To achieve a rate of 400MHz on the output of the divider, we +would have to set the rate of the PLL to 1.6 GHz and then divide +it by 4. The current code would set the PLL to 1.6GHz first while +the divider is still set to 1, thus causing the downstream +consumer of the clock to receive a few clock cycles of 1.6GHz +clock (far beyond it's maximum acceptable rate). We should be +changing the divider first before increasing the PLL rate to +avoid this problem. + +Therefore, set the rate of any child clocks that are increasing +in rate from their current rate so that they can increase their +dividers if necessary. We assume that there isn't such a thing as +minimum rate requirements. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/clk.c | 34 ++++++++++++++++++++++------------ + 1 file changed, 22 insertions(+), 12 deletions(-) + +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -1476,21 +1476,23 @@ static struct clk *clk_propagate_rate_ch + * walk down a subtree and set the new rates notifying the rate + * change on the way + */ +-static void clk_change_rate(struct clk *clk) ++static void clk_change_rate(struct clk *clk, unsigned long best_parent_rate) + { + struct clk *child; + struct hlist_node *tmp; + unsigned long old_rate; +- unsigned long best_parent_rate = 0; + bool skip_set_rate = false; + struct clk *old_parent; + +- old_rate = clk->rate; ++ hlist_for_each_entry(child, &clk->children, child_node) { ++ /* Skip children who will be reparented to another clock */ ++ if (child->new_parent && child->new_parent != clk) ++ continue; ++ if (child->new_rate > child->rate) ++ clk_change_rate(child, clk->new_rate); ++ } + +- if (clk->new_parent) +- best_parent_rate = clk->new_parent->rate; +- else if (clk->parent) +- best_parent_rate = clk->parent->rate; ++ old_rate = clk->rate; + + if (clk->new_parent && clk->new_parent != clk->parent) { + old_parent = __clk_set_parent_before(clk, clk->new_parent); +@@ -1510,7 +1512,7 @@ static void clk_change_rate(struct clk * + if (!skip_set_rate && clk->ops->set_rate) + clk->ops->set_rate(clk->hw, clk->new_rate, best_parent_rate); + +- clk->rate = clk_recalc(clk, best_parent_rate); ++ clk->rate = clk->new_rate; + + if (clk->notifier_count && old_rate != clk->rate) + __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate); +@@ -1523,12 +1525,13 @@ static void clk_change_rate(struct clk * + /* Skip children who will be reparented to another clock */ + if (child->new_parent && child->new_parent != clk) + continue; +- clk_change_rate(child); ++ if (child->new_rate != child->rate) ++ clk_change_rate(child, clk->new_rate); + } + + /* handle the new child who might not be in clk->children yet */ +- if (clk->new_child) +- clk_change_rate(clk->new_child); ++ if (clk->new_child && clk->new_child->new_rate != clk->new_child->rate) ++ clk_change_rate(clk->new_child, clk->new_rate); + } + + /** +@@ -1556,6 +1559,7 @@ int clk_set_rate(struct clk *clk, unsign + { + struct clk *top, *fail_clk; + int ret = 0; ++ unsigned long parent_rate; + + if (!clk) + return 0; +@@ -1589,8 +1593,13 @@ int clk_set_rate(struct clk *clk, unsign + goto out; + } + ++ if (top->parent) ++ parent_rate = top->parent->rate; ++ else ++ parent_rate = 0; ++ + /* change the rates */ +- clk_change_rate(top); ++ clk_change_rate(top, parent_rate); + + out: + clk_prepare_unlock(); diff --git a/target/linux/ipq806x/patches-3.18/136-clk-Add-safe-switch-hook.patch b/target/linux/ipq806x/patches-3.18/136-clk-Add-safe-switch-hook.patch new file mode 100644 index 0000000..227f8ce --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/136-clk-Add-safe-switch-hook.patch @@ -0,0 +1,170 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,04/13] clk: Add safe switch hook +From: Stephen Boyd +X-Patchwork-Id: 6063211 +Message-Id: <1426920332-9340-5-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:23 -0700 + +Sometimes clocks can't accept their parent source turning off +while the source is reprogrammed to a different rate. Most +notably CPU clocks require a way to switch away from the current +PLL they're running on, reprogram that PLL to a new rate, and +then switch back to the PLL with the new rate once they're done. +Add a hook that drivers can implement allowing them to return a +'safe parent' that they can switch their parent to while the +upstream source is reprogrammed to support this. + +Signed-off-by: Stephen Boyd + +--- +This patch is good enough for Krait, but soon I'll need to +support a "safe rate" where we ask a clock what rate it needs to be running +at to be sure it's within voltage constraints. Right now safe parent +handles that problem on Krait, but on other platforms it won't work. + + drivers/clk/clk.c | 61 ++++++++++++++++++++++++++++++++++++++------ + include/linux/clk-provider.h | 1 + + 2 files changed, 54 insertions(+), 8 deletions(-) + +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -1350,7 +1350,8 @@ out: + static void clk_calc_subtree(struct clk *clk, unsigned long new_rate, + struct clk *new_parent, u8 p_index) + { +- struct clk *child; ++ struct clk *child, *parent; ++ struct clk_hw *parent_hw; + + clk->new_rate = new_rate; + clk->new_parent = new_parent; +@@ -1360,6 +1361,18 @@ static void clk_calc_subtree(struct clk + if (new_parent && new_parent != clk->parent) + new_parent->new_child = clk; + ++ if (clk->ops->get_safe_parent) { ++ parent_hw = clk->ops->get_safe_parent(clk->hw); ++ if (parent_hw) { ++ parent = parent_hw->clk; ++ p_index = clk_fetch_parent_index(clk, parent); ++ clk->safe_parent_index = p_index; ++ clk->safe_parent = parent; ++ } ++ } else { ++ clk->safe_parent = NULL; ++ } ++ + hlist_for_each_entry(child, &clk->children, child_node) { + child->new_rate = clk_recalc(child, new_rate); + clk_calc_subtree(child, child->new_rate, NULL, 0); +@@ -1439,17 +1452,47 @@ out: + * so that in case of an error we can walk down the whole tree again and + * abort the change. + */ +-static struct clk *clk_propagate_rate_change(struct clk *clk, unsigned long event) ++static struct clk *clk_propagate_rate_change(struct clk *clk, ++ unsigned long event) + { + struct clk *child, *tmp_clk, *fail_clk = NULL; ++ struct clk *old_parent; + int ret = NOTIFY_DONE; + +- if (clk->rate == clk->new_rate) ++ if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE) + return NULL; + ++ switch (event) { ++ case PRE_RATE_CHANGE: ++ if (clk->safe_parent) ++ clk->ops->set_parent(clk->hw, clk->safe_parent_index); ++ clk->old_rate = clk->rate; ++ break; ++ case POST_RATE_CHANGE: ++ if (clk->safe_parent) { ++ old_parent = __clk_set_parent_before(clk, ++ clk->new_parent); ++ if (clk->ops->set_rate_and_parent) { ++ clk->ops->set_rate_and_parent(clk->hw, ++ clk->new_rate, ++ clk->new_parent ? ++ clk->new_parent->rate : 0, ++ clk->new_parent_index); ++ } else if (clk->ops->set_parent) { ++ clk->ops->set_parent(clk->hw, ++ clk->new_parent_index); ++ } ++ __clk_set_parent_after(clk, clk->new_parent, ++ old_parent); ++ } ++ break; ++ } ++ + if (clk->notifier_count) { +- ret = __clk_notify(clk, event, clk->rate, clk->new_rate); +- if (ret & NOTIFY_STOP_MASK) ++ if (event != POST_RATE_CHANGE || clk->old_rate != clk->rate) ++ ret = __clk_notify(clk, event, clk->old_rate, ++ clk->new_rate); ++ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE) + fail_clk = clk; + } + +@@ -1494,7 +1537,8 @@ static void clk_change_rate(struct clk * + + old_rate = clk->rate; + +- if (clk->new_parent && clk->new_parent != clk->parent) { ++ if (clk->new_parent && clk->new_parent != clk->parent && ++ !clk->safe_parent) { + old_parent = __clk_set_parent_before(clk, clk->new_parent); + + if (clk->ops->set_rate_and_parent) { +@@ -1514,9 +1558,6 @@ static void clk_change_rate(struct clk * + + clk->rate = clk->new_rate; + +- if (clk->notifier_count && old_rate != clk->rate) +- __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate); +- + /* + * Use safe iteration, as change_rate can actually swap parents + * for certain clock types. +@@ -1601,6 +1642,8 @@ int clk_set_rate(struct clk *clk, unsign + /* change the rates */ + clk_change_rate(top, parent_rate); + ++ clk_propagate_rate_change(top, POST_RATE_CHANGE); ++ + out: + clk_prepare_unlock(); + +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -179,6 +179,7 @@ struct clk_ops { + struct clk **best_parent_clk); + int (*set_parent)(struct clk_hw *hw, u8 index); + u8 (*get_parent)(struct clk_hw *hw); ++ struct clk_hw *(*get_safe_parent)(struct clk_hw *hw); + int (*set_rate)(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); + int (*set_rate_and_parent)(struct clk_hw *hw, +--- a/include/linux/clk-private.h ++++ b/include/linux/clk-private.h +@@ -38,8 +38,11 @@ struct clk { + struct clk **parents; + u8 num_parents; + u8 new_parent_index; ++ u8 safe_parent_index; + unsigned long rate; ++ unsigned long old_rate; + unsigned long new_rate; ++ struct clk *safe_parent; + struct clk *new_parent; + struct clk *new_child; + unsigned long flags; diff --git a/target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch b/target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch new file mode 100644 index 0000000..701d5e7 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch @@ -0,0 +1,351 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs) +From: Stephen Boyd +X-Patchwork-Id: 6063261 +Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:24 -0700 + +HFPLLs are the main frequency source for Krait CPU clocks. Add +support for changing the rate of these PLLs. + +Signed-off-by: Stephen Boyd + +--- +I'd really like to get rid of __clk_hfpll_init_once() if possible... + + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++ + drivers/clk/qcom/clk-hfpll.h | 54 +++++++++ + 3 files changed, 308 insertions(+) + create mode 100644 drivers/clk/qcom/clk-hfpll.c + create mode 100644 drivers/clk/qcom/clk-hfpll.h + +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o + clk-qcom-y += clk-rcg.o + clk-qcom-y += clk-rcg2.o + clk-qcom-y += clk-branch.o ++clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + + obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o +--- /dev/null ++++ b/drivers/clk/qcom/clk-hfpll.c +@@ -0,0 +1,253 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clk-regmap.h" ++#include "clk-hfpll.h" ++ ++#define PLL_OUTCTRL BIT(0) ++#define PLL_BYPASSNL BIT(1) ++#define PLL_RESET_N BIT(2) ++ ++/* Initialize a HFPLL at a given rate and enable it. */ ++static void __clk_hfpll_init_once(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ ++ if (likely(h->init_done)) ++ return; ++ ++ /* Configure PLL parameters for integer mode. */ ++ if (hd->config_val) ++ regmap_write(regmap, hd->config_reg, hd->config_val); ++ regmap_write(regmap, hd->m_reg, 0); ++ regmap_write(regmap, hd->n_reg, 1); ++ ++ if (hd->user_reg) { ++ u32 regval = hd->user_val; ++ unsigned long rate; ++ ++ rate = __clk_get_rate(hw->clk); ++ ++ /* Pick the right VCO. */ ++ if (hd->user_vco_mask && rate > hd->low_vco_max_rate) ++ regval |= hd->user_vco_mask; ++ regmap_write(regmap, hd->user_reg, regval); ++ } ++ ++ if (hd->droop_reg) ++ regmap_write(regmap, hd->droop_reg, hd->droop_val); ++ ++ h->init_done = true; ++} ++ ++static void __clk_hfpll_enable(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 val; ++ ++ __clk_hfpll_init_once(hw); ++ ++ /* Disable PLL bypass mode. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL); ++ ++ /* ++ * H/W requires a 5us delay between disabling the bypass and ++ * de-asserting the reset. Delay 10us just to be safe. ++ */ ++ udelay(10); ++ ++ /* De-assert active-low PLL reset. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N); ++ ++ /* Wait for PLL to lock. */ ++ if (hd->status_reg) { ++ do { ++ regmap_read(regmap, hd->status_reg, &val); ++ } while (!(val & BIT(hd->lock_bit))); ++ } else { ++ udelay(60); ++ } ++ ++ /* Enable PLL output. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL); ++} ++ ++/* Enable an already-configured HFPLL. */ ++static int clk_hfpll_enable(struct clk_hw *hw) ++{ ++ unsigned long flags; ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ regmap_read(regmap, hd->mode_reg, &mode); ++ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL))) ++ __clk_hfpll_enable(hw); ++ spin_unlock_irqrestore(&h->lock, flags); ++ ++ return 0; ++} ++ ++static void __clk_hfpll_disable(struct clk_hfpll *h) ++{ ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ ++ /* ++ * Disable the PLL output, disable test mode, enable the bypass mode, ++ * and assert the reset. ++ */ ++ regmap_update_bits(regmap, hd->mode_reg, ++ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0); ++} ++ ++static void clk_hfpll_disable(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ __clk_hfpll_disable(h); ++ spin_unlock_irqrestore(&h->lock, flags); ++} ++ ++static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ unsigned long rrate; ++ ++ rate = clamp(rate, hd->min_rate, hd->max_rate); ++ ++ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate; ++ if (rrate > hd->max_rate) ++ rrate -= *parent_rate; ++ ++ return rrate; ++} ++ ++/* ++ * For optimization reasons, assumes no downstream clocks are actively using ++ * it. ++ */ ++static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ unsigned long flags; ++ u32 l_val, val; ++ bool enabled; ++ ++ l_val = rate / parent_rate; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ ++ enabled = __clk_is_enabled(hw->clk); ++ if (enabled) ++ __clk_hfpll_disable(h); ++ ++ /* Pick the right VCO. */ ++ if (hd->user_reg && hd->user_vco_mask) { ++ regmap_read(regmap, hd->user_reg, &val); ++ if (rate <= hd->low_vco_max_rate) ++ val &= ~hd->user_vco_mask; ++ else ++ val |= hd->user_vco_mask; ++ regmap_write(regmap, hd->user_reg, val); ++ } ++ ++ regmap_write(regmap, hd->l_reg, l_val); ++ ++ if (enabled) ++ __clk_hfpll_enable(hw); ++ ++ spin_unlock_irqrestore(&h->lock, flags); ++ ++ return 0; ++} ++ ++static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 l_val; ++ ++ regmap_read(regmap, hd->l_reg, &l_val); ++ ++ return l_val * parent_rate; ++} ++ ++static void clk_hfpll_init(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode, status; ++ ++ regmap_read(regmap, hd->mode_reg, &mode); ++ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) { ++ __clk_hfpll_init_once(hw); ++ return; ++ } ++ ++ if (hd->status_reg) { ++ regmap_read(regmap, hd->status_reg, &status); ++ if (!(status & BIT(hd->lock_bit))) { ++ WARN(1, "HFPLL %s is ON, but not locked!\n", ++ __clk_get_name(hw->clk)); ++ clk_hfpll_disable(hw); ++ __clk_hfpll_init_once(hw); ++ } ++ } ++} ++ ++static int hfpll_is_enabled(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode; ++ ++ regmap_read(regmap, hd->mode_reg, &mode); ++ mode &= 0x7; ++ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL); ++} ++ ++const struct clk_ops clk_ops_hfpll = { ++ .enable = clk_hfpll_enable, ++ .disable = clk_hfpll_disable, ++ .is_enabled = hfpll_is_enabled, ++ .round_rate = clk_hfpll_round_rate, ++ .set_rate = clk_hfpll_set_rate, ++ .recalc_rate = clk_hfpll_recalc_rate, ++ .init = clk_hfpll_init, ++}; ++EXPORT_SYMBOL_GPL(clk_ops_hfpll); +--- /dev/null ++++ b/drivers/clk/qcom/clk-hfpll.h +@@ -0,0 +1,54 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#ifndef __QCOM_CLK_HFPLL_H__ ++#define __QCOM_CLK_HFPLL_H__ ++ ++#include ++#include ++#include "clk-regmap.h" ++ ++struct hfpll_data { ++ u32 mode_reg; ++ u32 l_reg; ++ u32 m_reg; ++ u32 n_reg; ++ u32 user_reg; ++ u32 droop_reg; ++ u32 config_reg; ++ u32 status_reg; ++ u8 lock_bit; ++ ++ u32 droop_val; ++ u32 config_val; ++ u32 user_val; ++ u32 user_vco_mask; ++ unsigned long low_vco_max_rate; ++ ++ unsigned long min_rate; ++ unsigned long max_rate; ++}; ++ ++struct clk_hfpll { ++ struct hfpll_data const *d; ++ int init_done; ++ ++ struct clk_regmap clkr; ++ spinlock_t lock; ++}; ++ ++#define to_clk_hfpll(_hw) \ ++ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr) ++ ++extern const struct clk_ops clk_ops_hfpll; ++ ++#endif diff --git a/target/linux/ipq806x/patches-3.18/138-clk-qcom-Add-HFPLL-driver.patch b/target/linux/ipq806x/patches-3.18/138-clk-qcom-Add-HFPLL-driver.patch new file mode 100644 index 0000000..a0b1d64 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/138-clk-qcom-Add-HFPLL-driver.patch @@ -0,0 +1,206 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,06/13] clk: qcom: Add HFPLL driver +From: Stephen Boyd +X-Patchwork-Id: 6063231 +Message-Id: <1426920332-9340-7-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:25 -0700 + +On some devices (MSM8974 for example), the HFPLLs are +instantiated within the Krait processor subsystem as separate +register regions. Add a driver for these PLLs so that we can +provide HFPLL clocks for use by the system. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/clock/qcom,hfpll.txt | 40 ++++++++ + drivers/clk/qcom/Kconfig | 8 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/hfpll.c | 109 +++++++++++++++++++++ + 4 files changed, 158 insertions(+) + create mode 100644 Documentation/devicetree/bindings/clock/qcom,hfpll.txt + create mode 100644 drivers/clk/qcom/hfpll.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt +@@ -0,0 +1,40 @@ ++High-Frequency PLL (HFPLL) ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be "qcom,hfpll" ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: address and size of HPLL registers. An optional second ++ element specifies the address and size of the alias ++ register region. ++ ++- clock-output-names: ++ Usage: required ++ Value type: ++ Definition: Name of the PLL. Typically hfpllX where X is a CPU number ++ starting at 0. Otherwise hfpll_Y where Y is more specific ++ such as "l2". ++ ++Example: ++ ++1) An HFPLL for the L2 cache. ++ ++ clock-controller@f9016000 { ++ compatible = "qcom,hfpll"; ++ reg = <0xf9016000 0x30>; ++ clock-output-names = "hfpll_l2"; ++ }; ++ ++2) An HFPLL for CPU0. This HFPLL has the alias register region. ++ ++ clock-controller@f908a000 { ++ compatible = "qcom,hfpll"; ++ reg = <0xf908a000 0x30>, <0xf900a000 0x30>; ++ clock-output-names = "hfpll0"; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -70,3 +70,11 @@ config MSM_MMCC_8974 + Support for the multimedia clock controller on msm8974 devices. + Say Y if you want to support multimedia devices such as display, + graphics, video encode/decode, camera, etc. ++ ++config QCOM_HFPLL ++ tristate "High-Frequency PLL (HFPLL) Clock Controller" ++ depends on COMMON_CLK_QCOM ++ help ++ Support for the high-frequency PLLs present on Qualcomm devices. ++ Say Y if you want to support CPU frequency scaling on devices ++ such as MSM8974, APQ8084, etc. +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -17,3 +17,4 @@ obj-$(CONFIG_MSM_GCC_8960) += gcc-msm896 + obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o + obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o ++obj-$(CONFIG_QCOM_HFPLL) += hfpll.o +--- /dev/null ++++ b/drivers/clk/qcom/hfpll.c +@@ -0,0 +1,109 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clk-regmap.h" ++#include "clk-hfpll.h" ++ ++static const struct hfpll_data hdata = { ++ .mode_reg = 0x00, ++ .l_reg = 0x04, ++ .m_reg = 0x08, ++ .n_reg = 0x0c, ++ .user_reg = 0x10, ++ .config_reg = 0x14, ++ .config_val = 0x430405d, ++ .status_reg = 0x1c, ++ .lock_bit = 16, ++ ++ .user_val = 0x8, ++ .user_vco_mask = 0x100000, ++ .low_vco_max_rate = 1248000000, ++ .min_rate = 537600000UL, ++ .max_rate = 2900000000UL, ++}; ++ ++static const struct of_device_id qcom_hfpll_match_table[] = { ++ { .compatible = "qcom,hfpll" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table); ++ ++static const struct regmap_config hfpll_regmap_config = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x30, ++ .fast_io = true, ++}; ++ ++static int qcom_hfpll_probe(struct platform_device *pdev) ++{ ++ struct clk *clk; ++ struct resource *res; ++ struct device *dev = &pdev->dev; ++ void __iomem *base; ++ struct regmap *regmap; ++ struct clk_hfpll *h; ++ struct clk_init_data init = { ++ .parent_names = (const char *[]){ "xo" }, ++ .num_parents = 1, ++ .ops = &clk_ops_hfpll, ++ }; ++ ++ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); ++ if (!h) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ if (of_property_read_string_index(dev->of_node, "clock-output-names", ++ 0, &init.name)) ++ return -ENODEV; ++ ++ h->d = &hdata; ++ h->clkr.hw.init = &init; ++ spin_lock_init(&h->lock); ++ ++ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static struct platform_driver qcom_hfpll_driver = { ++ .probe = qcom_hfpll_probe, ++ .driver = { ++ .name = "qcom-hfpll", ++ .of_match_table = qcom_hfpll_match_table, ++ }, ++}; ++module_platform_driver(qcom_hfpll_driver); ++ ++MODULE_DESCRIPTION("QCOM HFPLL Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:qcom-hfpll"); diff --git a/target/linux/ipq806x/patches-3.18/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch b/target/linux/ipq806x/patches-3.18/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch new file mode 100644 index 0000000..7fd53d1 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch @@ -0,0 +1,127 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,08/13] clk: qcom: Add IPQ806X's HFPLLs +From: Stephen Boyd +X-Patchwork-Id: 6063241 +Message-Id: <1426920332-9340-9-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:27 -0700 + +Describe the HFPLLs present on IPQ806X devices. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/qcom/gcc-ipq806x.c | 83 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 83 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -30,6 +30,7 @@ + #include "clk-pll.h" + #include "clk-rcg.h" + #include "clk-branch.h" ++#include "clk-hfpll.h" + #include "reset.h" + + static struct clk_pll pll0 = { +@@ -102,6 +103,85 @@ static struct clk_regmap pll8_vote = { + }, + }; + ++static struct hfpll_data hfpll0_data = { ++ .mode_reg = 0x3200, ++ .l_reg = 0x3208, ++ .m_reg = 0x320c, ++ .n_reg = 0x3210, ++ .config_reg = 0x3204, ++ .status_reg = 0x321c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3214, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll0 = { ++ .d = &hfpll0_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll0", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock), ++}; ++ ++static struct hfpll_data hfpll1_data = { ++ .mode_reg = 0x3240, ++ .l_reg = 0x3248, ++ .m_reg = 0x324c, ++ .n_reg = 0x3250, ++ .config_reg = 0x3244, ++ .status_reg = 0x325c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3314, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll1 = { ++ .d = &hfpll1_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll1", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock), ++}; ++ ++static struct hfpll_data hfpll_l2_data = { ++ .mode_reg = 0x3300, ++ .l_reg = 0x3308, ++ .m_reg = 0x330c, ++ .n_reg = 0x3310, ++ .config_reg = 0x3304, ++ .status_reg = 0x331c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3314, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll_l2 = { ++ .d = &hfpll_l2_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll_l2", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock), ++}; ++ ++ + static struct clk_pll pll14 = { + .l_reg = 0x31c4, + .m_reg = 0x31c8, +@@ -2261,6 +2341,9 @@ static struct clk_regmap *gcc_ipq806x_cl + [USB_FS1_XCVR_SRC] = &usb_fs1_xcvr_clk_src.clkr, + [USB_FS1_XCVR_CLK] = &usb_fs1_xcvr_clk.clkr, + [USB_FS1_SYSTEM_CLK] = &usb_fs1_sys_clk.clkr, ++ [PLL9] = &hfpll0.clkr, ++ [PLL10] = &hfpll1.clkr, ++ [PLL12] = &hfpll_l2.clkr, + }; + + static const struct qcom_reset_map gcc_ipq806x_resets[] = { diff --git a/target/linux/ipq806x/patches-3.18/140-clk-qcom-Add-support-for-Krait-clocks.patch b/target/linux/ipq806x/patches-3.18/140-clk-qcom-Add-support-for-Krait-clocks.patch new file mode 100644 index 0000000..63292e8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/140-clk-qcom-Add-support-for-Krait-clocks.patch @@ -0,0 +1,271 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,09/13] clk: qcom: Add support for Krait clocks +From: Stephen Boyd +X-Patchwork-Id: 6063251 +Message-Id: <1426920332-9340-10-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:28 -0700 + +The Krait clocks are made up of a series of muxes and a divider +that choose between a fixed rate clock and dedicated HFPLLs for +each CPU. Instead of using mmio accesses to remux parents, the +Krait implementation exposes the remux control via cp15 +registers. Support these clocks. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/qcom/Kconfig | 4 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/clk-krait.c | 166 +++++++++++++++++++++++++++++++++++++++++++ + drivers/clk/qcom/clk-krait.h | 49 +++++++++++++ + 4 files changed, 220 insertions(+) + create mode 100644 drivers/clk/qcom/clk-krait.c + create mode 100644 drivers/clk/qcom/clk-krait.h + +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -78,3 +78,7 @@ config QCOM_HFPLL + Support for the high-frequency PLLs present on Qualcomm devices. + Say Y if you want to support CPU frequency scaling on devices + such as MSM8974, APQ8084, etc. ++ ++config KRAIT_CLOCKS ++ bool ++ select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -6,6 +6,7 @@ clk-qcom-y += clk-pll.o + clk-qcom-y += clk-rcg.o + clk-qcom-y += clk-rcg2.o + clk-qcom-y += clk-branch.o ++clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o + clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + +--- /dev/null ++++ b/drivers/clk/qcom/clk-krait.c +@@ -0,0 +1,166 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "clk-krait.h" ++ ++/* Secondary and primary muxes share the same cp15 register */ ++static DEFINE_SPINLOCK(krait_clock_reg_lock); ++ ++#define LPL_SHIFT 8 ++static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel) ++{ ++ unsigned long flags; ++ u32 regval; ++ ++ spin_lock_irqsave(&krait_clock_reg_lock, flags); ++ regval = krait_get_l2_indirect_reg(mux->offset); ++ regval &= ~(mux->mask << mux->shift); ++ regval |= (sel & mux->mask) << mux->shift; ++ if (mux->lpl) { ++ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT)); ++ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT); ++ } ++ krait_set_l2_indirect_reg(mux->offset, regval); ++ spin_unlock_irqrestore(&krait_clock_reg_lock, flags); ++ ++ /* Wait for switch to complete. */ ++ mb(); ++ udelay(1); ++} ++ ++static int krait_mux_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ u32 sel; ++ ++ sel = clk_mux_reindex(index, mux->parent_map, 0); ++ mux->en_mask = sel; ++ /* Don't touch mux if CPU is off as it won't work */ ++ if (__clk_is_enabled(hw->clk)) ++ __krait_mux_set_sel(mux, sel); ++ return 0; ++} ++ ++static u8 krait_mux_get_parent(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ u32 sel; ++ ++ sel = krait_get_l2_indirect_reg(mux->offset); ++ sel >>= mux->shift; ++ sel &= mux->mask; ++ mux->en_mask = sel; ++ ++ return clk_mux_get_parent(hw, sel, mux->parent_map, 0); ++} ++ ++static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw) ++{ ++ int i; ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ int num_parents = __clk_get_num_parents(hw->clk); ++ ++ i = mux->safe_sel; ++ for (i = 0; i < num_parents; i++) ++ if (mux->safe_sel == mux->parent_map[i]) ++ break; ++ ++ return __clk_get_hw(clk_get_parent_by_index(hw->clk, i)); ++} ++ ++static int krait_mux_enable(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ ++ __krait_mux_set_sel(mux, mux->en_mask); ++ ++ return 0; ++} ++ ++static void krait_mux_disable(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ ++ __krait_mux_set_sel(mux, mux->safe_sel); ++} ++ ++const struct clk_ops krait_mux_clk_ops = { ++ .enable = krait_mux_enable, ++ .disable = krait_mux_disable, ++ .set_parent = krait_mux_set_parent, ++ .get_parent = krait_mux_get_parent, ++ .determine_rate = __clk_mux_determine_rate_closest, ++ .get_safe_parent = krait_mux_get_safe_parent, ++}; ++EXPORT_SYMBOL_GPL(krait_mux_clk_ops); ++ ++/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */ ++static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ *parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), rate * 2); ++ return DIV_ROUND_UP(*parent_rate, 2); ++} ++ ++static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct krait_div2_clk *d = to_krait_div2_clk(hw); ++ unsigned long flags; ++ u32 val; ++ u32 mask = BIT(d->width) - 1; ++ ++ if (d->lpl) ++ mask = mask << (d->shift + LPL_SHIFT) | mask << d->shift; ++ ++ spin_lock_irqsave(&krait_clock_reg_lock, flags); ++ val = krait_get_l2_indirect_reg(d->offset); ++ val &= ~mask; ++ krait_set_l2_indirect_reg(d->offset, val); ++ spin_unlock_irqrestore(&krait_clock_reg_lock, flags); ++ ++ return 0; ++} ++ ++static unsigned long ++krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) ++{ ++ struct krait_div2_clk *d = to_krait_div2_clk(hw); ++ u32 mask = BIT(d->width) - 1; ++ u32 div; ++ ++ div = krait_get_l2_indirect_reg(d->offset); ++ div >>= d->shift; ++ div &= mask; ++ div = (div + 1) * 2; ++ ++ return DIV_ROUND_UP(parent_rate, div); ++} ++ ++const struct clk_ops krait_div2_clk_ops = { ++ .round_rate = krait_div2_round_rate, ++ .set_rate = krait_div2_set_rate, ++ .recalc_rate = krait_div2_recalc_rate, ++}; ++EXPORT_SYMBOL_GPL(krait_div2_clk_ops); +--- /dev/null ++++ b/drivers/clk/qcom/clk-krait.h +@@ -0,0 +1,49 @@ ++/* ++ * Copyright (c) 2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __QCOM_CLK_KRAIT_H ++#define __QCOM_CLK_KRAIT_H ++ ++#include ++ ++struct krait_mux_clk { ++ unsigned int *parent_map; ++ bool has_safe_parent; ++ u8 safe_sel; ++ u32 offset; ++ u32 mask; ++ u32 shift; ++ u32 en_mask; ++ bool lpl; ++ ++ struct clk_hw hw; ++}; ++ ++#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw) ++ ++extern const struct clk_ops krait_mux_clk_ops; ++ ++struct krait_div2_clk { ++ u32 offset; ++ u8 width; ++ u32 shift; ++ bool lpl; ++ ++ struct clk_hw hw; ++}; ++ ++#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw) ++ ++extern const struct clk_ops krait_div2_clk_ops; ++ ++#endif diff --git a/target/linux/ipq806x/patches-3.18/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch b/target/linux/ipq806x/patches-3.18/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch new file mode 100644 index 0000000..06b14d8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch @@ -0,0 +1,205 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,10/13] clk: qcom: Add KPSS ACC/GCC driver +From: Stephen Boyd +X-Patchwork-Id: 6063201 +Message-Id: <1426920332-9340-11-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:29 -0700 + +The ACC and GCC regions present in KPSSv1 contain registers to +control clocks and power to each Krait CPU and L2. For CPUfreq +purposes probe these devices and expose a mux clock that chooses +between PXO and PLL8. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/arm/msm/qcom,kpss-acc.txt | 7 ++ + .../devicetree/bindings/arm/msm/qcom,kpss-gcc.txt | 28 +++++++ + drivers/clk/qcom/Kconfig | 8 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/kpss-xcc.c | 95 ++++++++++++++++++++++ + 5 files changed, 139 insertions(+) + create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt + create mode 100644 drivers/clk/qcom/kpss-xcc.c + +--- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt +@@ -21,10 +21,17 @@ PROPERTIES + the register region. An optional second element specifies + the base address and size of the alias register region. + ++- clock-output-names: ++ Usage: optional ++ Value type: ++ Definition: Name of the output clock. Typically acpuX_aux where X is a ++ CPU number starting at 0. ++ + Example: + + clock-controller@2088000 { + compatible = "qcom,kpss-acc-v2"; + reg = <0x02088000 0x1000>, + <0x02008000 0x1000>; ++ clock-output-names = "acpu0_aux"; + }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt +@@ -0,0 +1,28 @@ ++Krait Processor Sub-system (KPSS) Global Clock Controller (GCC) ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: should be one of: ++ "qcom,kpss-gcc" ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: base address and size of the register region ++ ++- clock-output-names: ++ Usage: required ++ Value type: ++ Definition: Name of the output clock. Typically acpu_l2_aux indicating ++ an L2 cache auxiliary clock. ++ ++Example: ++ ++ l2cc: clock-controller@2011000 { ++ compatible = "qcom,kpss-gcc"; ++ reg = <0x2011000 0x1000>; ++ clock-output-names = "acpu_l2_aux"; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -79,6 +79,14 @@ config QCOM_HFPLL + Say Y if you want to support CPU frequency scaling on devices + such as MSM8974, APQ8084, etc. + ++config KPSS_XCC ++ tristate "KPSS Clock Controller" ++ depends on COMMON_CLK_QCOM ++ help ++ Support for the Krait ACC and GCC clock controllers. Say Y ++ if you want to support CPU frequency scaling on devices such ++ as MSM8960, APQ8064, etc. ++ + config KRAIT_CLOCKS + bool + select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -18,4 +18,5 @@ obj-$(CONFIG_MSM_GCC_8960) += gcc-msm896 + obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o + obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o ++obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o + obj-$(CONFIG_QCOM_HFPLL) += hfpll.o +--- /dev/null ++++ b/drivers/clk/qcom/kpss-xcc.c +@@ -0,0 +1,95 @@ ++/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static const char *aux_parents[] = { ++ "pll8_vote", ++ "pxo", ++}; ++ ++static unsigned int aux_parent_map[] = { ++ 3, ++ 0, ++}; ++ ++static const struct of_device_id kpss_xcc_match_table[] = { ++ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL }, ++ { .compatible = "qcom,kpss-gcc" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, kpss_xcc_match_table); ++ ++static int kpss_xcc_driver_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *id; ++ struct clk *clk; ++ struct resource *res; ++ void __iomem *base; ++ const char *name; ++ ++ id = of_match_device(kpss_xcc_match_table, &pdev->dev); ++ if (!id) ++ return -ENODEV; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ if (id->data) { ++ if (of_property_read_string_index(pdev->dev.of_node, ++ "clock-output-names", 0, &name)) ++ return -ENODEV; ++ base += 0x14; ++ } else { ++ name = "acpu_l2_aux"; ++ base += 0x28; ++ } ++ ++ clk = clk_register_mux_table(&pdev->dev, name, aux_parents, ++ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3, ++ 0, aux_parent_map, NULL); ++ ++ platform_set_drvdata(pdev, clk); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static int kpss_xcc_driver_remove(struct platform_device *pdev) ++{ ++ clk_unregister_mux(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static struct platform_driver kpss_xcc_driver = { ++ .probe = kpss_xcc_driver_probe, ++ .remove = kpss_xcc_driver_remove, ++ .driver = { ++ .name = "kpss-xcc", ++ .of_match_table = kpss_xcc_match_table, ++ }, ++}; ++module_platform_driver(kpss_xcc_driver); ++ ++MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:kpss-xcc"); diff --git a/target/linux/ipq806x/patches-3.18/142-clk-qcom-Add-Krait-clock-controller-driver.patch b/target/linux/ipq806x/patches-3.18/142-clk-qcom-Add-Krait-clock-controller-driver.patch new file mode 100644 index 0000000..98a09ac --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/142-clk-qcom-Add-Krait-clock-controller-driver.patch @@ -0,0 +1,435 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,11/13] clk: qcom: Add Krait clock controller driver +From: Stephen Boyd +X-Patchwork-Id: 6063121 +Message-Id: <1426920332-9340-12-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:30 -0700 + +The Krait CPU clocks are made up of a primary mux and secondary +mux for each CPU and the L2, controlled via cp15 accessors. For +Kraits within KPSSv1 each secondary mux accepts a different aux +source, but on KPSSv2 each secondary mux accepts the same aux +source. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/clock/qcom,krait-cc.txt | 22 ++ + drivers/clk/qcom/Kconfig | 8 + + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/krait-cc.c | 352 +++++++++++++++++++++ + 4 files changed, 383 insertions(+) + create mode 100644 Documentation/devicetree/bindings/clock/qcom,krait-cc.txt + create mode 100644 drivers/clk/qcom/krait-cc.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt +@@ -0,0 +1,22 @@ ++Krait Clock Controller ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be one of: ++ "qcom,krait-cc-v1" ++ "qcom,krait-cc-v2" ++ ++- #clock-cells: ++ Usage: required ++ Value type: ++ Definition: must be 1 ++ ++Example: ++ ++ kraitcc: clock-controller { ++ compatible = "qcom,krait-cc-v1"; ++ #clock-cells = <1>; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -87,6 +87,14 @@ config KPSS_XCC + if you want to support CPU frequency scaling on devices such + as MSM8960, APQ8064, etc. + ++config KRAITCC ++ tristate "Krait Clock Controller" ++ depends on COMMON_CLK_QCOM && ARM ++ select KRAIT_CLOCKS ++ help ++ Support for the Krait CPU clocks on Qualcomm devices. ++ Say Y if you want to support CPU frequency scaling. ++ + config KRAIT_CLOCKS + bool + select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -20,3 +20,4 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8 + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o + obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o + obj-$(CONFIG_QCOM_HFPLL) += hfpll.o ++obj-$(CONFIG_KRAITCC) += krait-cc.o +--- /dev/null ++++ b/drivers/clk/qcom/krait-cc.c +@@ -0,0 +1,352 @@ ++/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clk-krait.h" ++ ++static unsigned int sec_mux_map[] = { ++ 2, ++ 0, ++}; ++ ++static unsigned int pri_mux_map[] = { ++ 1, ++ 2, ++ 0, ++}; ++ ++static int ++krait_add_div(struct device *dev, int id, const char *s, unsigned offset) ++{ ++ struct krait_div2_clk *div; ++ struct clk_init_data init = { ++ .num_parents = 1, ++ .ops = &krait_div2_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ const char *p_names[1]; ++ struct clk *clk; ++ ++ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL); ++ if (!div) ++ return -ENOMEM; ++ ++ div->width = 2; ++ div->shift = 6; ++ div->lpl = id >= 0; ++ div->offset = offset; ++ div->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s); ++ if (!init.name) ++ return -ENOMEM; ++ ++ init.parent_names = p_names; ++ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s); ++ if (!p_names[0]) { ++ kfree(init.name); ++ return -ENOMEM; ++ } ++ ++ clk = devm_clk_register(dev, &div->hw); ++ kfree(p_names[0]); ++ kfree(init.name); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static int ++krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset, ++ bool unique_aux) ++{ ++ struct krait_mux_clk *mux; ++ static const char *sec_mux_list[] = { ++ "acpu_aux", ++ "qsb", ++ }; ++ struct clk_init_data init = { ++ .parent_names = sec_mux_list, ++ .num_parents = ARRAY_SIZE(sec_mux_list), ++ .ops = &krait_mux_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ struct clk *clk; ++ ++ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return -ENOMEM; ++ ++ mux->offset = offset; ++ mux->lpl = id >= 0; ++ mux->has_safe_parent = true; ++ mux->safe_sel = 2; ++ mux->mask = 0x3; ++ mux->shift = 2; ++ mux->parent_map = sec_mux_map; ++ mux->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); ++ if (!init.name) ++ return -ENOMEM; ++ ++ if (unique_aux) { ++ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s); ++ if (!sec_mux_list[0]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_aux; ++ } ++ } ++ ++ clk = devm_clk_register(dev, &mux->hw); ++ ++ if (unique_aux) ++ kfree(sec_mux_list[0]); ++err_aux: ++ kfree(init.name); ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static struct clk * ++krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset) ++{ ++ struct krait_mux_clk *mux; ++ const char *p_names[3]; ++ struct clk_init_data init = { ++ .parent_names = p_names, ++ .num_parents = ARRAY_SIZE(p_names), ++ .ops = &krait_mux_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ struct clk *clk; ++ ++ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return ERR_PTR(-ENOMEM); ++ ++ mux->has_safe_parent = true; ++ mux->safe_sel = 0; ++ mux->mask = 0x3; ++ mux->shift = 0; ++ mux->offset = offset; ++ mux->lpl = id >= 0; ++ mux->parent_map = pri_mux_map; ++ mux->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s); ++ if (!init.name) ++ return ERR_PTR(-ENOMEM); ++ ++ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s); ++ if (!p_names[0]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p0; ++ } ++ ++ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s); ++ if (!p_names[1]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p1; ++ } ++ ++ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); ++ if (!p_names[2]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p2; ++ } ++ ++ clk = devm_clk_register(dev, &mux->hw); ++ ++ kfree(p_names[2]); ++err_p2: ++ kfree(p_names[1]); ++err_p1: ++ kfree(p_names[0]); ++err_p0: ++ kfree(init.name); ++ return clk; ++} ++ ++/* id < 0 for L2, otherwise id == physical CPU number */ ++static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux) ++{ ++ int ret; ++ unsigned offset; ++ void *p = NULL; ++ const char *s; ++ struct clk *clk; ++ ++ if (id >= 0) { ++ offset = 0x4501 + (0x1000 * id); ++ s = p = kasprintf(GFP_KERNEL, "%d", id); ++ if (!s) ++ return ERR_PTR(-ENOMEM); ++ } else { ++ offset = 0x500; ++ s = "_l2"; ++ } ++ ++ ret = krait_add_div(dev, id, s, offset); ++ if (ret) { ++ clk = ERR_PTR(ret); ++ goto err; ++ } ++ ++ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux); ++ if (ret) { ++ clk = ERR_PTR(ret); ++ goto err; ++ } ++ ++ clk = krait_add_pri_mux(dev, id, s, offset); ++err: ++ kfree(p); ++ return clk; ++} ++ ++static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data) ++{ ++ unsigned int idx = clkspec->args[0]; ++ struct clk **clks = data; ++ ++ if (idx >= 5) { ++ pr_err("%s: invalid clock index %d\n", __func__, idx); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return clks[idx] ? : ERR_PTR(-ENODEV); ++} ++ ++static const struct of_device_id krait_cc_match_table[] = { ++ { .compatible = "qcom,krait-cc-v1", (void *)1UL }, ++ { .compatible = "qcom,krait-cc-v2" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, krait_cc_match_table); ++ ++static int krait_cc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_device_id *id; ++ unsigned long cur_rate, aux_rate; ++ int cpu; ++ struct clk *clk; ++ struct clk **clks; ++ struct clk *l2_pri_mux_clk; ++ ++ id = of_match_device(krait_cc_match_table, dev); ++ if (!id) ++ return -ENODEV; ++ ++ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */ ++ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ ++ if (!id->data) { ++ clk = clk_register_fixed_factor(dev, "acpu_aux", ++ "gpll0_vote", 0, 1, 2); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ } ++ ++ /* Krait configurations have at most 4 CPUs and one L2 */ ++ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL); ++ if (!clks) ++ return -ENOMEM; ++ ++ for_each_possible_cpu(cpu) { ++ clk = krait_add_clks(dev, cpu, id->data); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ clks[cpu] = clk; ++ } ++ ++ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data); ++ if (IS_ERR(l2_pri_mux_clk)) ++ return PTR_ERR(l2_pri_mux_clk); ++ clks[4] = l2_pri_mux_clk; ++ ++ /* ++ * We don't want the CPU or L2 clocks to be turned off at late init ++ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the ++ * refcount of these clocks. Any cpufreq/hotplug manager can assume ++ * that the clocks have already been prepared and enabled by the time ++ * they take over. ++ */ ++ for_each_online_cpu(cpu) { ++ clk_prepare_enable(l2_pri_mux_clk); ++ WARN(clk_prepare_enable(clks[cpu]), ++ "Unable to turn on CPU%d clock", cpu); ++ } ++ ++ /* ++ * Force reinit of HFPLLs and muxes to overwrite any potential ++ * incorrect configuration of HFPLLs and muxes by the bootloader. ++ * While at it, also make sure the cores are running at known rates ++ * and print the current rate. ++ * ++ * The clocks are set to aux clock rate first to make sure the ++ * secondary mux is not sourcing off of QSB. The rate is then set to ++ * two different rates to force a HFPLL reinit under all ++ * circumstances. ++ */ ++ cur_rate = clk_get_rate(l2_pri_mux_clk); ++ aux_rate = 384000000; ++ if (cur_rate == 1) { ++ pr_info("L2 @ QSB rate. Forcing new rate.\n"); ++ cur_rate = aux_rate; ++ } ++ clk_set_rate(l2_pri_mux_clk, aux_rate); ++ clk_set_rate(l2_pri_mux_clk, 2); ++ clk_set_rate(l2_pri_mux_clk, cur_rate); ++ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000); ++ for_each_possible_cpu(cpu) { ++ clk = clks[cpu]; ++ cur_rate = clk_get_rate(clk); ++ if (cur_rate == 1) { ++ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu); ++ cur_rate = aux_rate; ++ } ++ clk_set_rate(clk, aux_rate); ++ clk_set_rate(clk, 2); ++ clk_set_rate(clk, cur_rate); ++ pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000); ++ } ++ ++ of_clk_add_provider(dev->of_node, krait_of_get, clks); ++ ++ return 0; ++} ++ ++static struct platform_driver krait_cc_driver = { ++ .probe = krait_cc_probe, ++ .driver = { ++ .name = "krait-cc", ++ .of_match_table = krait_cc_match_table, ++ }, ++}; ++module_platform_driver(krait_cc_driver); ++ ++MODULE_DESCRIPTION("Krait CPU Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:krait-cc"); diff --git a/target/linux/ipq806x/patches-3.18/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch b/target/linux/ipq806x/patches-3.18/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch new file mode 100644 index 0000000..c3ca9b5 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch @@ -0,0 +1,304 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,12/13] cpufreq: Add module to register cpufreq on Krait CPUs +From: Stephen Boyd +X-Patchwork-Id: 6063191 +Message-Id: <1426920332-9340-13-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:31 -0700 + +Register a cpufreq-generic device whenever we detect that a +"qcom,krait" compatible CPU is present in DT. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/arm/msm/qcom,pvs.txt | 38 ++++ + drivers/cpufreq/Kconfig.arm | 9 + + drivers/cpufreq/Makefile | 1 + + drivers/cpufreq/qcom-cpufreq.c | 204 +++++++++++++++++++++ + 4 files changed, 252 insertions(+) + create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt + create mode 100644 drivers/cpufreq/qcom-cpufreq.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt +@@ -0,0 +1,38 @@ ++Qualcomm Process Voltage Scaling Tables ++ ++The node name is required to be "qcom,pvs". There shall only be one ++such node present in the root of the tree. ++ ++PROPERTIES ++ ++- qcom,pvs-format-a or qcom,pvs-format-b: ++ Usage: required ++ Value type: ++ Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties. ++ If qcom,pvs-format-a is used the table is two columns ++ (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage, ++ and current in that order). ++ ++- qcom,speedX-pvsY-bin-vZ: ++ Usage: required ++ Value type: ++ Definition: The PVS table corresponding to the speed bin X, pvs bin Y, ++ and version Z. ++Example: ++ ++ qcom,pvs { ++ qcom,pvs-format-a; ++ qcom,speed0-pvs0-bin-v0 = ++ < 384000000 950000 >, ++ < 486000000 975000 >, ++ < 594000000 1000000 >, ++ < 702000000 1025000 >, ++ < 810000000 1075000 >, ++ < 918000000 1100000 >, ++ < 1026000000 1125000 >, ++ < 1134000000 1175000 >, ++ < 1242000000 1200000 >, ++ < 1350000000 1225000 >, ++ < 1458000000 1237500 >, ++ < 1512000000 1250000 >; ++ }; +--- a/drivers/cpufreq/Kconfig.arm ++++ b/drivers/cpufreq/Kconfig.arm +@@ -129,6 +129,15 @@ config ARM_OMAP2PLUS_CPUFREQ + depends on ARCH_OMAP2PLUS + default ARCH_OMAP2PLUS + ++config ARM_QCOM_CPUFREQ ++ tristate "Qualcomm based" ++ depends on ARCH_QCOM ++ select PM_OPP ++ help ++ This adds the CPUFreq driver for Qualcomm SoC based boards. ++ ++ If in doubt, say N. ++ + config ARM_S3C_CPUFREQ + bool + help +--- a/drivers/cpufreq/Makefile ++++ b/drivers/cpufreq/Makefile +@@ -64,6 +64,7 @@ obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += o + obj-$(CONFIG_PXA25x) += pxa2xx-cpufreq.o + obj-$(CONFIG_PXA27x) += pxa2xx-cpufreq.o + obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o ++obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o + obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o + obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o + obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o +--- /dev/null ++++ b/drivers/cpufreq/qcom-cpufreq.c +@@ -0,0 +1,204 @@ ++/* Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver) ++{ ++ void __iomem *base; ++ u32 pte_efuse; ++ ++ *speed = *pvs = *pvs_ver = 0; ++ ++ base = ioremap(0x007000c0, 4); ++ if (!base) { ++ pr_warn("Unable to read efuse data. Defaulting to 0!\n"); ++ return; ++ } ++ ++ pte_efuse = readl_relaxed(base); ++ iounmap(base); ++ ++ *speed = pte_efuse & 0xf; ++ if (*speed == 0xf) ++ *speed = (pte_efuse >> 4) & 0xf; ++ ++ if (*speed == 0xf) { ++ *speed = 0; ++ pr_warn("Speed bin: Defaulting to %d\n", *speed); ++ } else { ++ pr_info("Speed bin: %d\n", *speed); ++ } ++ ++ *pvs = (pte_efuse >> 10) & 0x7; ++ if (*pvs == 0x7) ++ *pvs = (pte_efuse >> 13) & 0x7; ++ ++ if (*pvs == 0x7) { ++ *pvs = 0; ++ pr_warn("PVS bin: Defaulting to %d\n", *pvs); ++ } else { ++ pr_info("PVS bin: %d\n", *pvs); ++ } ++} ++ ++static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver) ++{ ++ u32 pte_efuse, redundant_sel; ++ void __iomem *base; ++ ++ *speed = 0; ++ *pvs = 0; ++ *pvs_ver = 0; ++ ++ base = ioremap(0xfc4b80b0, 8); ++ if (!base) { ++ pr_warn("Unable to read efuse data. Defaulting to 0!\n"); ++ return; ++ } ++ ++ pte_efuse = readl_relaxed(base); ++ redundant_sel = (pte_efuse >> 24) & 0x7; ++ *speed = pte_efuse & 0x7; ++ /* 4 bits of PVS are in efuse register bits 31, 8-6. */ ++ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); ++ *pvs_ver = (pte_efuse >> 4) & 0x3; ++ ++ switch (redundant_sel) { ++ case 1: ++ *speed = (pte_efuse >> 27) & 0xf; ++ break; ++ case 2: ++ *pvs = (pte_efuse >> 27) & 0xf; ++ break; ++ } ++ ++ /* Check SPEED_BIN_BLOW_STATUS */ ++ if (pte_efuse & BIT(3)) { ++ pr_info("Speed bin: %d\n", *speed); ++ } else { ++ pr_warn("Speed bin not set. Defaulting to 0!\n"); ++ *speed = 0; ++ } ++ ++ /* Check PVS_BLOW_STATUS */ ++ pte_efuse = readl_relaxed(base + 0x4) & BIT(21); ++ if (pte_efuse) { ++ pr_info("PVS bin: %d\n", *pvs); ++ } else { ++ pr_warn("PVS bin not set. Defaulting to 0!\n"); ++ *pvs = 0; ++ } ++ ++ pr_info("PVS version: %d\n", *pvs_ver); ++ iounmap(base); ++} ++ ++static int __init qcom_cpufreq_populate_opps(void) ++{ ++ int len, rows, cols, i, k, speed, pvs, pvs_ver; ++ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX"; ++ struct device_node *np; ++ struct device *dev; ++ int cpu = 0; ++ ++ np = of_find_node_by_name(NULL, "qcom,pvs"); ++ if (!np) ++ return -ENODEV; ++ ++ if (of_property_read_bool(np, "qcom,pvs-format-a")) { ++ get_krait_bin_format_a(&speed, &pvs, &pvs_ver); ++ cols = 2; ++ } else if (of_property_read_bool(np, "qcom,pvs-format-b")) { ++ get_krait_bin_format_b(&speed, &pvs, &pvs_ver); ++ cols = 3; ++ } else { ++ return -ENODEV; ++ } ++ ++ snprintf(table_name, sizeof(table_name), ++ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver); ++ ++ if (!of_find_property(np, table_name, &len)) ++ return -EINVAL; ++ ++ len /= sizeof(u32); ++ if (len % cols || len == 0) ++ return -EINVAL; ++ ++ rows = len / cols; ++ ++ for (i = 0, k = 0; i < rows; i++) { ++ u32 freq, volt; ++ ++ of_property_read_u32_index(np, table_name, k++, &freq); ++ of_property_read_u32_index(np, table_name, k++, &volt); ++ while (k % cols) ++ k++; /* Skip uA entries if present */ ++ for (cpu = 0; cpu < num_possible_cpus(); cpu++) { ++ dev = get_cpu_device(cpu); ++ if (!dev) ++ return -ENODEV; ++ if (dev_pm_opp_add(dev, freq, volt)) ++ pr_warn("failed to add OPP %u\n", freq); ++ } ++ } ++ ++ return 0; ++} ++ ++static int __init qcom_cpufreq_driver_init(void) ++{ ++ struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; ++ struct platform_device_info devinfo = { ++ .name = "cpufreq-dt", ++ .data = &pdata, ++ .size_data = sizeof(pdata), ++ }; ++ struct device *cpu_dev; ++ struct device_node *np; ++ int ret; ++ ++ cpu_dev = get_cpu_device(0); ++ if (!cpu_dev) ++ return -ENODEV; ++ ++ np = of_node_get(cpu_dev->of_node); ++ if (!np) ++ return -ENOENT; ++ ++ if (!of_device_is_compatible(np, "qcom,krait")) { ++ of_node_put(np); ++ return -ENODEV; ++ } ++ of_node_put(np); ++ ++ ret = qcom_cpufreq_populate_opps(); ++ if (ret) ++ return ret; ++ ++ return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo)); ++} ++module_init(qcom_cpufreq_driver_init); ++ ++MODULE_DESCRIPTION("Qualcomm CPUfreq driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/ipq806x/patches-3.18/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch b/target/linux/ipq806x/patches-3.18/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch new file mode 100644 index 0000000..a3c3bbf --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch @@ -0,0 +1,100 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -24,6 +24,11 @@ + next-level-cache = <&L2>; + qcom,acc = <&acc0>; + qcom,saw = <&saw0>; ++ clocks = <&kraitcc 0>; ++ clock-names = "cpu"; ++ clock-latency = <100000>; ++ core-supply = <&smb208_s2a>; ++ voltage-tolerance = <5>; + }; + + cpu@1 { +@@ -34,11 +39,24 @@ + next-level-cache = <&L2>; + qcom,acc = <&acc1>; + qcom,saw = <&saw1>; ++ clocks = <&kraitcc 1>; ++ clock-names = "cpu"; ++ clock-latency = <100000>; ++ core-supply = <&smb208_s2b>; + }; + + L2: l2-cache { + compatible = "cache"; + cache-level = <2>; ++ clocks = <&kraitcc 4>; ++ clock-names = "cache"; ++ cache-points-kHz = < ++ /* kHz uV CPU kHz */ ++ 1200000 1150000 1200000 ++ 1000000 1100000 600000 ++ 384000 1100000 384000 ++ >; ++ vdd_dig-supply = <&smb208_s1a>; + }; + }; + +@@ -71,6 +89,46 @@ + }; + }; + ++ kraitcc: clock-controller { ++ compatible = "qcom,krait-cc-v1"; ++ #clock-cells = <1>; ++ }; ++ ++ qcom,pvs { ++ qcom,pvs-format-a; ++ qcom,speed0-pvs0-bin-v0 = ++ < 1400000000 1250000 >, ++ < 1200000000 1200000 >, ++ < 1000000000 1150000 >, ++ < 800000000 1100000 >, ++ < 600000000 1050000 >, ++ < 384000000 1000000 >; ++ ++ qcom,speed0-pvs1-bin-v0 = ++ < 1400000000 1175000 >, ++ < 1200000000 1125000 >, ++ < 1000000000 1075000 >, ++ < 800000000 1025000 >, ++ < 600000000 975000 >, ++ < 384000000 925000 >; ++ ++ qcom,speed0-pvs2-bin-v0 = ++ < 1400000000 1125000 >, ++ < 1200000000 1075000 >, ++ < 1000000000 1025000 >, ++ < 800000000 995000 >, ++ < 600000000 925000 >, ++ < 384000000 875000 >; ++ ++ qcom,speed0-pvs3-bin-v0 = ++ < 1400000000 1050000 >, ++ < 1200000000 1000000 >, ++ < 1000000000 950000 >, ++ < 800000000 900000 >, ++ < 600000000 850000 >, ++ < 384000000 800000 >; ++ }; ++ + soc: soc { + #address-cells = <1>; + #size-cells = <1>; +@@ -171,11 +229,13 @@ + acc0: clock-controller@2088000 { + compatible = "qcom,kpss-acc-v1"; + reg = <0x02088000 0x1000>, <0x02008000 0x1000>; ++ clock-output-names = "acpu0_aux"; + }; + + acc1: clock-controller@2098000 { + compatible = "qcom,kpss-acc-v1"; + reg = <0x02098000 0x1000>, <0x02008000 0x1000>; ++ clock-output-names = "acpu1_aux"; + }; + + l2cc: clock-controller@2011000 { diff --git a/target/linux/ipq806x/patches-3.18/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch b/target/linux/ipq806x/patches-3.18/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch new file mode 100644 index 0000000..6052b41 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch @@ -0,0 +1,461 @@ +From dd77db4143290689d3a5e1ec61627233d0711b66 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd +Date: Fri, 30 May 2014 16:36:11 -0700 +Subject: [PATCH] FROMLIST: cpufreq: Add a cpufreq-krait based on cpufreq-cpu0 + +Krait processors have individual clocks for each CPU that can +scale independently from one another. cpufreq-cpu0 is fairly +close to this, but assumes that there is only one clock for all +CPUs. Add a driver to support the Krait configuration. + +TODO: Merge into cpufreq-cpu0? Or make generic? + +Signed-off-by: Stephen Boyd + +--- + drivers/cpufreq/Kconfig | 13 +++ + drivers/cpufreq/Makefile | 1 + + drivers/cpufreq/cpufreq-krait.c | 190 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 204 insertions(+) + create mode 100644 drivers/cpufreq/cpufreq-krait.c + +--- a/drivers/cpufreq/Kconfig ++++ b/drivers/cpufreq/Kconfig +@@ -196,6 +196,19 @@ config CPUFREQ_DT + + If in doubt, say N. + ++config GENERIC_CPUFREQ_KRAIT ++ tristate "Krait cpufreq driver" ++ depends on HAVE_CLK && OF ++ # if CPU_THERMAL is on and THERMAL=m, CPU0 cannot be =y: ++ depends on !CPU_THERMAL || THERMAL ++ select PM_OPP ++ help ++ This adds a generic cpufreq driver for CPU0 frequency management. ++ It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) ++ systems which share clock and voltage across all CPUs. ++ ++ If in doubt, say N. ++ + menu "x86 CPU frequency scaling drivers" + depends on X86 + source "drivers/cpufreq/Kconfig.x86" +--- a/drivers/cpufreq/Makefile ++++ b/drivers/cpufreq/Makefile +@@ -14,6 +14,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) + obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o + + obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o ++obj-$(CONFIG_GENERIC_CPUFREQ_KRAIT) += cpufreq-krait.o + + ################################################################################## + # x86 drivers. +--- /dev/null ++++ b/drivers/cpufreq/cpufreq-krait.c +@@ -0,0 +1,390 @@ ++/* ++ * Copyright (C) 2012 Freescale Semiconductor, Inc. ++ * Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * The OPP code in function krait_set_target() is reused from ++ * drivers/cpufreq/omap-cpufreq.c ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static unsigned int transition_latency; ++static unsigned int voltage_tolerance; /* in percentage */ ++ ++static struct device *cpu_dev; ++static DEFINE_PER_CPU(struct clk *, krait_cpu_clks); ++static DEFINE_PER_CPU(struct regulator *, krait_supply_core); ++static struct cpufreq_frequency_table *freq_table; ++static struct thermal_cooling_device *cdev; ++ ++struct cache_points { ++ unsigned long cache_freq; ++ unsigned int cache_volt; ++ unsigned long cpu_freq; ++}; ++ ++static struct regulator *krait_l2_reg; ++static struct clk *krait_l2_clk; ++static struct cache_points *krait_l2_points; ++static int nr_krait_l2_points; ++ ++static int krait_parse_cache_points(struct device *dev, ++ struct device_node *of_node) ++{ ++ const struct property *prop; ++ const __be32 *val; ++ int nr, i; ++ ++ prop = of_find_property(of_node, "cache-points-kHz", NULL); ++ if (!prop) ++ return -ENODEV; ++ if (!prop->value) ++ return -ENODATA; ++ ++ /* ++ * Each OPP is a set of tuples consisting of frequency and ++ * cpu-frequency like . ++ */ ++ nr = prop->length / sizeof(u32); ++ if (nr % 3) { ++ dev_err(dev, "%s: Invalid cache points\n", __func__); ++ return -EINVAL; ++ } ++ nr /= 3; ++ ++ krait_l2_points = devm_kcalloc(dev, nr, sizeof(*krait_l2_points), ++ GFP_KERNEL); ++ if (!krait_l2_points) ++ return -ENOMEM; ++ nr_krait_l2_points = nr; ++ ++ for (i = 0, val = prop->value; i < nr; i++) { ++ unsigned long cache_freq = be32_to_cpup(val++) * 1000; ++ unsigned int cache_volt = be32_to_cpup(val++); ++ unsigned long cpu_freq = be32_to_cpup(val++) * 1000; ++ ++ krait_l2_points[i].cache_freq = cache_freq; ++ krait_l2_points[i].cache_volt = cache_volt; ++ krait_l2_points[i].cpu_freq = cpu_freq; ++ } ++ ++ return 0; ++} ++ ++static int krait_set_target(struct cpufreq_policy *policy, unsigned int index) ++{ ++ struct dev_pm_opp *opp; ++ unsigned long volt = 0, volt_old = 0, tol = 0; ++ unsigned long freq, max_cpu_freq = 0; ++ unsigned int old_freq, new_freq; ++ long freq_Hz, freq_exact; ++ int ret, i; ++ struct clk *cpu_clk; ++ struct regulator *core; ++ unsigned int cpu; ++ ++ cpu_clk = per_cpu(krait_cpu_clks, policy->cpu); ++ ++ freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000); ++ if (freq_Hz <= 0) ++ freq_Hz = freq_table[index].frequency * 1000; ++ ++ freq_exact = freq_Hz; ++ new_freq = freq_Hz / 1000; ++ old_freq = clk_get_rate(cpu_clk) / 1000; ++ ++ core = per_cpu(krait_supply_core, policy->cpu); ++ ++ rcu_read_lock(); ++ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz); ++ if (IS_ERR(opp)) { ++ rcu_read_unlock(); ++ pr_err("failed to find OPP for %ld\n", freq_Hz); ++ return PTR_ERR(opp); ++ } ++ volt = dev_pm_opp_get_voltage(opp); ++ rcu_read_unlock(); ++ tol = volt * voltage_tolerance / 100; ++ volt_old = regulator_get_voltage(core); ++ ++ pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n", ++ old_freq / 1000, volt_old ? volt_old / 1000 : -1, ++ new_freq / 1000, volt ? volt / 1000 : -1); ++ ++ /* scaling up? scale voltage before frequency */ ++ if (new_freq > old_freq) { ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage up: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ ret = clk_set_rate(cpu_clk, freq_exact); ++ if (ret) { ++ pr_err("failed to set clock rate: %d\n", ret); ++ return ret; ++ } ++ ++ /* scaling down? scale voltage after frequency */ ++ if (new_freq < old_freq) { ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage down: %d\n", ret); ++ clk_set_rate(cpu_clk, old_freq * 1000); ++ } ++ } ++ ++ for_each_possible_cpu(cpu) { ++ freq = clk_get_rate(per_cpu(krait_cpu_clks, cpu)); ++ max_cpu_freq = max(max_cpu_freq, freq); ++ } ++ ++ for (i = 0; i < nr_krait_l2_points; i++) { ++ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) { ++ if (krait_l2_reg) { ++ ret = regulator_set_voltage_tol(krait_l2_reg, ++ krait_l2_points[i].cache_volt, ++ tol); ++ if (ret) { ++ pr_err("failed to scale l2 voltage: %d\n", ++ ret); ++ } ++ } ++ ret = clk_set_rate(krait_l2_clk, ++ krait_l2_points[i].cache_freq); ++ if (ret) ++ pr_err("failed to scale l2 clk: %d\n", ret); ++ break; ++ } ++ ++ } ++ ++ return ret; ++} ++ ++static int krait_cpufreq_init(struct cpufreq_policy *policy) ++{ ++ int ret; ++ ++ policy->clk = per_cpu(krait_cpu_clks, policy->cpu); ++ ++ ret = cpufreq_table_validate_and_show(policy, freq_table); ++ if (ret) { ++ pr_err("%s: invalid frequency table: %d\n", __func__, ret); ++ return ret; ++ } ++ ++ policy->cpuinfo.transition_latency = transition_latency; ++ ++ return 0; ++} ++ ++static struct cpufreq_driver krait_cpufreq_driver = { ++ .flags = CPUFREQ_STICKY, ++ .verify = cpufreq_generic_frequency_table_verify, ++ .target_index = krait_set_target, ++ .get = cpufreq_generic_get, ++ .init = krait_cpufreq_init, ++ .name = "generic_krait", ++ .attr = cpufreq_generic_attr, ++}; ++ ++static int krait_cpufreq_probe(struct platform_device *pdev) ++{ ++ struct device_node *np, *cache; ++ int ret, i; ++ unsigned int cpu; ++ struct device *dev; ++ struct clk *clk; ++ struct regulator *core; ++ unsigned long freq_Hz, freq, max_cpu_freq = 0; ++ struct dev_pm_opp *opp; ++ unsigned long volt, tol; ++ ++ cpu_dev = get_cpu_device(0); ++ if (!cpu_dev) { ++ pr_err("failed to get krait device\n"); ++ return -ENODEV; ++ } ++ ++ np = of_node_get(cpu_dev->of_node); ++ if (!np) { ++ pr_err("failed to find krait node\n"); ++ return -ENOENT; ++ } ++ ++ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); ++ if (ret) { ++ pr_err("failed to init cpufreq table: %d\n", ret); ++ goto out_put_node; ++ } ++ ++ of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); ++ ++ if (of_property_read_u32(np, "clock-latency", &transition_latency)) ++ transition_latency = CPUFREQ_ETERNAL; ++ ++ cache = of_find_next_cache_node(np); ++ if (cache) { ++ struct device_node *vdd; ++ ++ vdd = of_parse_phandle(cache, "vdd_dig-supply", 0); ++ if (vdd) { ++ krait_l2_reg = regulator_get(NULL, vdd->name); ++ if (IS_ERR(krait_l2_reg)) { ++ pr_warn("failed to get l2 vdd_dig supply\n"); ++ krait_l2_reg = NULL; ++ } ++ of_node_put(vdd); ++ } ++ ++ krait_l2_clk = of_clk_get(cache, 0); ++ if (!IS_ERR(krait_l2_clk)) { ++ ret = krait_parse_cache_points(&pdev->dev, cache); ++ if (ret) ++ clk_put(krait_l2_clk); ++ } ++ if (IS_ERR(krait_l2_clk) || ret) ++ krait_l2_clk = NULL; ++ } ++ ++ for_each_possible_cpu(cpu) { ++ dev = get_cpu_device(cpu); ++ if (!dev) { ++ pr_err("failed to get krait device\n"); ++ ret = -ENOENT; ++ goto out_free_table; ++ } ++ per_cpu(krait_cpu_clks, cpu) = clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(clk)) { ++ ret = PTR_ERR(clk); ++ goto out_free_table; ++ } ++ core = devm_regulator_get(dev, "core"); ++ if (IS_ERR(core)) { ++ pr_debug("failed to get core regulator\n"); ++ ret = PTR_ERR(core); ++ goto out_free_table; ++ } ++ per_cpu(krait_supply_core, cpu) = core; ++ ++ freq = freq_Hz = clk_get_rate(clk); ++ ++ rcu_read_lock(); ++ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz); ++ if (IS_ERR(opp)) { ++ rcu_read_unlock(); ++ pr_err("failed to find OPP for %ld\n", freq_Hz); ++ ret = PTR_ERR(opp); ++ goto out_free_table; ++ } ++ volt = dev_pm_opp_get_voltage(opp); ++ rcu_read_unlock(); ++ ++ tol = volt * voltage_tolerance / 100; ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage up: %d\n", ret); ++ goto out_free_table; ++ } ++ ret = regulator_enable(core); ++ if (ret) { ++ pr_err("failed to enable regulator: %d\n", ret); ++ goto out_free_table; ++ } ++ max_cpu_freq = max(max_cpu_freq, freq); ++ } ++ ++ for (i = 0; i < nr_krait_l2_points; i++) { ++ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) { ++ if (krait_l2_reg) { ++ ret = regulator_set_voltage_tol(krait_l2_reg, ++ krait_l2_points[i].cache_volt, ++ tol); ++ if (ret) ++ pr_err("failed to scale l2 voltage: %d\n", ++ ret); ++ ret = regulator_enable(krait_l2_reg); ++ if (ret) ++ pr_err("failed to enable l2 voltage: %d\n", ++ ret); ++ } ++ break; ++ } ++ ++ } ++ ++ ret = cpufreq_register_driver(&krait_cpufreq_driver); ++ if (ret) { ++ pr_err("failed register driver: %d\n", ret); ++ goto out_free_table; ++ } ++ of_node_put(np); ++ ++ /* ++ * For now, just loading the cooling device; ++ * thermal DT code takes care of matching them. ++ */ ++ for_each_possible_cpu(cpu) { ++ dev = get_cpu_device(cpu); ++ np = of_node_get(dev->of_node); ++ if (of_find_property(np, "#cooling-cells", NULL)) { ++ cdev = of_cpufreq_cooling_register(np, cpumask_of(cpu)); ++ if (IS_ERR(cdev)) ++ pr_err("running cpufreq without cooling device: %ld\n", ++ PTR_ERR(cdev)); ++ } ++ of_node_put(np); ++ } ++ ++ return 0; ++ ++out_free_table: ++ regulator_put(krait_l2_reg); ++ clk_put(krait_l2_clk); ++ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); ++out_put_node: ++ of_node_put(np); ++ return ret; ++} ++ ++static int krait_cpufreq_remove(struct platform_device *pdev) ++{ ++ cpufreq_cooling_unregister(cdev); ++ cpufreq_unregister_driver(&krait_cpufreq_driver); ++ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); ++ clk_put(krait_l2_clk); ++ regulator_put(krait_l2_reg); ++ ++ return 0; ++} ++ ++static struct platform_driver krait_cpufreq_platdrv = { ++ .driver = { ++ .name = "cpufreq-krait", ++ .owner = THIS_MODULE, ++ }, ++ .probe = krait_cpufreq_probe, ++ .remove = krait_cpufreq_remove, ++}; ++module_platform_driver(krait_cpufreq_platdrv); ++ ++MODULE_DESCRIPTION("Krait CPUfreq driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/cpufreq/qcom-cpufreq.c ++++ b/drivers/cpufreq/qcom-cpufreq.c +@@ -168,11 +168,8 @@ static int __init qcom_cpufreq_populate_ + + static int __init qcom_cpufreq_driver_init(void) + { +- struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; + struct platform_device_info devinfo = { +- .name = "cpufreq-dt", +- .data = &pdata, +- .size_data = sizeof(pdata), ++ .name = "cpufreq-krait", + }; + struct device *cpu_dev; + struct device_node *np; diff --git a/target/linux/ipq806x/patches-3.18/150-dmaengine-Rework-dma_chan_get.patch b/target/linux/ipq806x/patches-3.18/150-dmaengine-Rework-dma_chan_get.patch new file mode 100644 index 0000000..880e67c --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/150-dmaengine-Rework-dma_chan_get.patch @@ -0,0 +1,70 @@ +From d2f4f99db3e9ec8b063cf2e45704e2bb95428317 Mon Sep 17 00:00:00 2001 +From: Maxime Ripard +Date: Mon, 17 Nov 2014 14:41:58 +0100 +Subject: [PATCH] dmaengine: Rework dma_chan_get + +dma_chan_get uses a rather interesting error handling and code path. + +Change it to something more usual in the kernel. + +Signed-off-by: Maxime Ripard +Acked-by: Laurent Pinchart +Signed-off-by: Vinod Koul +--- + drivers/dma/dmaengine.c | 36 +++++++++++++++++++----------------- + 1 file changed, 19 insertions(+), 17 deletions(-) + +--- a/drivers/dma/dmaengine.c ++++ b/drivers/dma/dmaengine.c +@@ -222,31 +222,33 @@ static void balance_ref_count(struct dma + */ + static int dma_chan_get(struct dma_chan *chan) + { +- int err = -ENODEV; + struct module *owner = dma_chan_to_owner(chan); ++ int ret; + ++ /* The channel is already in use, update client count */ + if (chan->client_count) { + __module_get(owner); +- err = 0; +- } else if (try_module_get(owner)) +- err = 0; ++ goto out; ++ } + +- if (err == 0) +- chan->client_count++; ++ if (!try_module_get(owner)) ++ return -ENODEV; + + /* allocate upon first client reference */ +- if (chan->client_count == 1 && err == 0) { +- int desc_cnt = chan->device->device_alloc_chan_resources(chan); +- +- if (desc_cnt < 0) { +- err = desc_cnt; +- chan->client_count = 0; +- module_put(owner); +- } else if (!dma_has_cap(DMA_PRIVATE, chan->device->cap_mask)) +- balance_ref_count(chan); +- } +- +- return err; ++ ret = chan->device->device_alloc_chan_resources(chan); ++ if (ret < 0) ++ goto err_out; ++ ++ if (!dma_has_cap(DMA_PRIVATE, chan->device->cap_mask)) ++ balance_ref_count(chan); ++ ++out: ++ chan->client_count++; ++ return 0; ++ ++err_out: ++ module_put(owner); ++ return ret; + } + + /** diff --git a/target/linux/ipq806x/patches-3.18/151-dmaengine-Remove-the-need-to-declare-device_control.patch b/target/linux/ipq806x/patches-3.18/151-dmaengine-Remove-the-need-to-declare-device_control.patch new file mode 100644 index 0000000..b24a10b --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/151-dmaengine-Remove-the-need-to-declare-device_control.patch @@ -0,0 +1,27 @@ +From 4f8ef9f4140cc286d7d1cf9237da7a7439e4fc0b Mon Sep 17 00:00:00 2001 +From: Maxime Ripard +Date: Mon, 17 Nov 2014 14:42:03 +0100 +Subject: [PATCH] dmaengine: Remove the need to declare device_control + +In order to migrate the drivers without triggering a BUG_ON for the converted +drivers, which would cause bisectability issues, we need to remove that check +before removing the device_control function entirely. + +Signed-off-by: Maxime Ripard +Acked-by: Laurent Pinchart +Signed-off-by: Vinod Koul +--- + drivers/dma/dmaengine.c | 2 -- + 1 file changed, 2 deletions(-) + +--- a/drivers/dma/dmaengine.c ++++ b/drivers/dma/dmaengine.c +@@ -814,8 +814,6 @@ int dma_async_device_register(struct dma + !device->device_prep_dma_sg); + BUG_ON(dma_has_cap(DMA_CYCLIC, device->cap_mask) && + !device->device_prep_dma_cyclic); +- BUG_ON(dma_has_cap(DMA_SLAVE, device->cap_mask) && +- !device->device_control); + BUG_ON(dma_has_cap(DMA_INTERLEAVE, device->cap_mask) && + !device->device_prep_interleaved_dma); + diff --git a/target/linux/ipq806x/patches-3.18/152-dmaengine-Make-channel-allocation-callbacks-optional.patch b/target/linux/ipq806x/patches-3.18/152-dmaengine-Make-channel-allocation-callbacks-optional.patch new file mode 100644 index 0000000..6337259 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/152-dmaengine-Make-channel-allocation-callbacks-optional.patch @@ -0,0 +1,62 @@ +From c4b54a648e682f678c338619df848233a6babc46 Mon Sep 17 00:00:00 2001 +From: Maxime Ripard +Date: Mon, 17 Nov 2014 14:41:59 +0100 +Subject: [PATCH] dmaengine: Make channel allocation callbacks optional + +Nowadays, some drivers don't have anything in there channel allocation +callbacks anymore. + +Remove the BUG_ON if those callbacks aren't implemented, in order to allow +drivers to not implement them. + +Signed-off-by: Maxime Ripard +Acked-by: Laurent Pinchart +Signed-off-by: Vinod Koul +--- + drivers/dma/dmaengine.c | 18 +++++++++++------- + 1 file changed, 11 insertions(+), 7 deletions(-) + +--- a/drivers/dma/dmaengine.c ++++ b/drivers/dma/dmaengine.c +@@ -235,9 +235,11 @@ static int dma_chan_get(struct dma_chan + return -ENODEV; + + /* allocate upon first client reference */ +- ret = chan->device->device_alloc_chan_resources(chan); +- if (ret < 0) +- goto err_out; ++ if (chan->device->device_alloc_chan_resources) { ++ ret = chan->device->device_alloc_chan_resources(chan); ++ if (ret < 0) ++ goto err_out; ++ } + + if (!dma_has_cap(DMA_PRIVATE, chan->device->cap_mask)) + balance_ref_count(chan); +@@ -259,11 +261,15 @@ err_out: + */ + static void dma_chan_put(struct dma_chan *chan) + { ++ /* This channel is not in use, bail out */ + if (!chan->client_count) +- return; /* this channel failed alloc_chan_resources */ ++ return; ++ + chan->client_count--; + module_put(dma_chan_to_owner(chan)); +- if (chan->client_count == 0) ++ ++ /* This channel is not in use anymore, free it */ ++ if (!chan->client_count && chan->device->device_free_chan_resources) + chan->device->device_free_chan_resources(chan); + } + +@@ -817,8 +823,6 @@ int dma_async_device_register(struct dma + BUG_ON(dma_has_cap(DMA_INTERLEAVE, device->cap_mask) && + !device->device_prep_interleaved_dma); + +- BUG_ON(!device->device_alloc_chan_resources); +- BUG_ON(!device->device_free_chan_resources); + BUG_ON(!device->device_tx_status); + BUG_ON(!device->device_issue_pending); + BUG_ON(!device->dev); diff --git a/target/linux/ipq806x/patches-3.18/153-dmaengine-Introduce-a-device_config-callback.patch b/target/linux/ipq806x/patches-3.18/153-dmaengine-Introduce-a-device_config-callback.patch new file mode 100644 index 0000000..ced452f --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/153-dmaengine-Introduce-a-device_config-callback.patch @@ -0,0 +1,51 @@ +From 94a73e30dfe6722e9f4ef19f7892901d7d00eab1 Mon Sep 17 00:00:00 2001 +From: Maxime Ripard +Date: Mon, 17 Nov 2014 14:42:00 +0100 +Subject: [PATCH] dmaengine: Introduce a device_config callback + +The fact that the channel configuration is done in device_control is rather +misleading, since it's not really advertised as such, plus, the fact that the +framework exposes a function of its own makes it not really intuitive, while +we're losing the type checking whenever we pass that unsigned long argument. + +Add a device_config callback to dma_device, with a fallback on the old +behaviour for now for existing drivers to opt in. + +Signed-off-by: Maxime Ripard +Acked-by: Laurent Pinchart +Signed-off-by: Vinod Koul +--- + include/linux/dmaengine.h | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/include/linux/dmaengine.h ++++ b/include/linux/dmaengine.h +@@ -607,6 +607,8 @@ struct dma_tx_state { + * The function takes a buffer of size buf_len. The callback function will + * be called after period_len bytes have been transferred. + * @device_prep_interleaved_dma: Transfer expression in a generic way. ++ * @device_config: Pushes a new configuration to a channel, return 0 or an error ++ * code + * @device_control: manipulate all pending operations on a channel, returns + * zero or error code + * @device_tx_status: poll for transaction completion, the optional +@@ -673,6 +675,9 @@ struct dma_device { + struct dma_async_tx_descriptor *(*device_prep_interleaved_dma)( + struct dma_chan *chan, struct dma_interleaved_template *xt, + unsigned long flags); ++ ++ int (*device_config)(struct dma_chan *chan, ++ struct dma_slave_config *config); + int (*device_control)(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg); + +@@ -696,6 +701,9 @@ static inline int dmaengine_device_contr + static inline int dmaengine_slave_config(struct dma_chan *chan, + struct dma_slave_config *config) + { ++ if (chan->device->device_config) ++ return chan->device->device_config(chan, config); ++ + return dmaengine_device_control(chan, DMA_SLAVE_CONFIG, + (unsigned long)config); + } diff --git a/target/linux/ipq806x/patches-3.18/154-dmaengine-Add-device_terminate_all-callback.patch b/target/linux/ipq806x/patches-3.18/154-dmaengine-Add-device_terminate_all-callback.patch new file mode 100644 index 0000000..422f7fb --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/154-dmaengine-Add-device_terminate_all-callback.patch @@ -0,0 +1,47 @@ +From 7fa0cf462daa6f6121b332b87833d7f5bdb515c0 Mon Sep 17 00:00:00 2001 +From: Maxime Ripard +Date: Mon, 17 Nov 2014 14:42:02 +0100 +Subject: [PATCH] dmaengine: Add device_terminate_all callback + +Split out the terminate_all command from device_control to a dma_device +callback. In order to preserve backward capability, still rely on +device_control if no such callback has been implemented. + +Eventually, this will allow to create a generic dma_slave_caps callback. + +Signed-off-by: Maxime Ripard +Acked-by: Laurent Pinchart +Signed-off-by: Vinod Koul +--- + include/linux/dmaengine.h | 6 ++++++ + 1 file changed, 6 insertions(+) + +--- a/include/linux/dmaengine.h ++++ b/include/linux/dmaengine.h +@@ -611,6 +611,8 @@ struct dma_tx_state { + * code + * @device_control: manipulate all pending operations on a channel, returns + * zero or error code ++ * @device_terminate_all: Aborts all transfers on a channel. Returns 0 ++ * or an error code + * @device_tx_status: poll for transaction completion, the optional + * txstate parameter can be supplied with a pointer to get a + * struct with auxiliary transfer status information, otherwise the call +@@ -680,6 +682,7 @@ struct dma_device { + struct dma_slave_config *config); + int (*device_control)(struct dma_chan *chan, enum dma_ctrl_cmd cmd, + unsigned long arg); ++ int (*device_terminate_all)(struct dma_chan *chan); + + enum dma_status (*device_tx_status)(struct dma_chan *chan, + dma_cookie_t cookie, +@@ -789,6 +792,9 @@ static inline int dma_get_slave_caps(str + + static inline int dmaengine_terminate_all(struct dma_chan *chan) + { ++ if (chan->device->device_terminate_all) ++ return chan->device->device_terminate_all(chan); ++ + return dmaengine_device_control(chan, DMA_TERMINATE_ALL, 0); + } + diff --git a/target/linux/ipq806x/patches-3.18/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch b/target/linux/ipq806x/patches-3.18/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch new file mode 100644 index 0000000..4f5c0ef --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch @@ -0,0 +1,76 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v6,1/2] dt/bindings: qcom_adm: Fix channel specifiers +From: Andy Gross +X-Patchwork-Id: 6027361 +Message-Id: <1426571172-9711-2-git-send-email-agross@codeaurora.org> +To: Vinod Koul +Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org, + linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, Kumar Gala , + Bjorn Andersson , + Andy Gross +Date: Tue, 17 Mar 2015 00:46:11 -0500 + +This patch removes the crci information from the dma channel property. At least +one client device requires using more than one CRCI value for a channel. This +does not match the current binding and the crci information needs to be removed. + +Instead, the client device will provide this information via other means. + +Signed-off-by: Andy Gross + +--- +Documentation/devicetree/bindings/dma/qcom_adm.txt | 16 ++++++---------- + 1 file changed, 6 insertions(+), 10 deletions(-) + +--- a/Documentation/devicetree/bindings/dma/qcom_adm.txt ++++ b/Documentation/devicetree/bindings/dma/qcom_adm.txt +@@ -4,8 +4,7 @@ Required properties: + - compatible: must contain "qcom,adm" for IPQ/APQ8064 and MSM8960 + - reg: Address range for DMA registers + - interrupts: Should contain one interrupt shared by all channels +-- #dma-cells: must be <2>. First cell denotes the channel number. Second cell +- denotes CRCI (client rate control interface) flow control assignment. ++- #dma-cells: must be <1>. First cell denotes the channel number. + - clocks: Should contain the core clock and interface clock. + - clock-names: Must contain "core" for the core clock and "iface" for the + interface clock. +@@ -22,7 +21,7 @@ Example: + compatible = "qcom,adm"; + reg = <0x18300000 0x100000>; + interrupts = <0 170 0>; +- #dma-cells = <2>; ++ #dma-cells = <1>; + + clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>; + clock-names = "core", "iface"; +@@ -35,15 +34,12 @@ Example: + qcom,ee = <0>; + }; + +-DMA clients must use the format descripted in the dma.txt file, using a three ++DMA clients must use the format descripted in the dma.txt file, using a two + cell specifier for each channel. + +-Each dmas request consists of 3 cells: ++Each dmas request consists of two cells: + 1. phandle pointing to the DMA controller + 2. channel number +- 3. CRCI assignment, if applicable. If no CRCI flow control is required, use 0. +- The CRCI is used for flow control. It identifies the peripheral device that +- is the source/destination for the transferred data. + + Example: + +@@ -56,7 +52,7 @@ Example: + + cs-gpios = <&qcom_pinmux 20 0>; + +- dmas = <&adm_dma 6 9>, +- <&adm_dma 5 10>; ++ dmas = <&adm_dma 6>, ++ <&adm_dma 5>; + dma-names = "rx", "tx"; + }; diff --git a/target/linux/ipq806x/patches-3.18/156-dmaengine-Add-ADM-driver.patch b/target/linux/ipq806x/patches-3.18/156-dmaengine-Add-ADM-driver.patch new file mode 100644 index 0000000..16d000e --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/156-dmaengine-Add-ADM-driver.patch @@ -0,0 +1,958 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v6,2/2] dmaengine: Add ADM driver +From: Andy Gross +X-Patchwork-Id: 6027351 +Message-Id: <1426571172-9711-3-git-send-email-agross@codeaurora.org> +To: Vinod Koul +Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org, + linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, Kumar Gala , + Bjorn Andersson , + Andy Gross +Date: Tue, 17 Mar 2015 00:46:12 -0500 + +Add the DMA engine driver for the QCOM Application Data Mover (ADM) DMA +controller found in the MSM8x60 and IPQ/APQ8064 platforms. + +The ADM supports both memory to memory transactions and memory +to/from peripheral device transactions. The controller also provides flow +control capabilities for transactions to/from peripheral devices. + +The initial release of this driver supports slave transfers to/from peripherals +and also incorporates CRCI (client rate control interface) flow control. + +Signed-off-by: Andy Gross +Reviewed-by: sricharan + +--- +drivers/dma/Kconfig | 10 + + drivers/dma/Makefile | 1 + + drivers/dma/qcom_adm.c | 900 ++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 911 insertions(+) + create mode 100644 drivers/dma/qcom_adm.c + +--- a/drivers/dma/Kconfig ++++ b/drivers/dma/Kconfig +@@ -457,4 +457,14 @@ config QCOM_BAM_DMA + Enable support for the QCOM BAM DMA controller. This controller + provides DMA capabilities for a variety of on-chip devices. + ++config QCOM_ADM ++ tristate "Qualcomm ADM support" ++ depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) ++ select DMA_ENGINE ++ select DMA_VIRTUAL_CHANNELS ++ ---help--- ++ Enable support for the Qualcomm ADM DMA controller. This controller ++ provides DMA capabilities for both general purpose and on-chip ++ peripheral devices. ++ + endif +--- /dev/null ++++ b/drivers/dma/qcom_adm.c +@@ -0,0 +1,896 @@ ++/* ++ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "dmaengine.h" ++#include "virt-dma.h" ++ ++/* ADM registers - calculated from channel number and security domain */ ++#define ADM_CHAN_MULTI 0x4 ++#define ADM_CI_MULTI 0x4 ++#define ADM_CRCI_MULTI 0x4 ++#define ADM_EE_MULTI 0x800 ++#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan) ++#define ADM_EE_OFFS(ee) (ADM_EE_MULTI * ee) ++#define ADM_CHAN_EE_OFFS(chan, ee) (ADM_CHAN_OFFS(chan) + ADM_EE_OFFS(ee)) ++#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan) ++#define ADM_CI_OFFS(ci) (ADM_CHAN_OFF(ci)) ++#define ADM_CH_CMD_PTR(chan, ee) (ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_RSLT(chan, ee) (0x40 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_FLUSH_STATE0(chan, ee) (0x80 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_STATUS_SD(chan, ee) (0x200 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_CONF(chan) (0x240 + ADM_CHAN_OFFS(chan)) ++#define ADM_CH_RSLT_CONF(chan, ee) (0x300 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_SEC_DOMAIN_IRQ_STATUS(ee) (0x380 + ADM_EE_OFFS(ee)) ++#define ADM_CI_CONF(ci) (0x390 + ci * ADM_CI_MULTI) ++#define ADM_GP_CTL 0x3d8 ++#define ADM_CRCI_CTL(crci, ee) (0x400 + crci * ADM_CRCI_MULTI + \ ++ ADM_EE_OFFS(ee)) ++ ++/* channel status */ ++#define ADM_CH_STATUS_VALID BIT(1) ++ ++/* channel result */ ++#define ADM_CH_RSLT_VALID BIT(31) ++#define ADM_CH_RSLT_ERR BIT(3) ++#define ADM_CH_RSLT_FLUSH BIT(2) ++#define ADM_CH_RSLT_TPD BIT(1) ++ ++/* channel conf */ ++#define ADM_CH_CONF_SHADOW_EN BIT(12) ++#define ADM_CH_CONF_MPU_DISABLE BIT(11) ++#define ADM_CH_CONF_PERM_MPU_CONF BIT(9) ++#define ADM_CH_CONF_FORCE_RSLT_EN BIT(7) ++#define ADM_CH_CONF_SEC_DOMAIN(ee) (((ee & 0x3) << 4) | ((ee & 0x4) << 11)) ++ ++/* channel result conf */ ++#define ADM_CH_RSLT_CONF_FLUSH_EN BIT(1) ++#define ADM_CH_RSLT_CONF_IRQ_EN BIT(0) ++ ++/* CRCI CTL */ ++#define ADM_CRCI_CTL_MUX_SEL BIT(18) ++#define ADM_CRCI_CTL_RST BIT(17) ++ ++/* CI configuration */ ++#define ADM_CI_RANGE_END(x) (x << 24) ++#define ADM_CI_RANGE_START(x) (x << 16) ++#define ADM_CI_BURST_4_WORDS BIT(2) ++#define ADM_CI_BURST_8_WORDS BIT(3) ++ ++/* GP CTL */ ++#define ADM_GP_CTL_LP_EN BIT(12) ++#define ADM_GP_CTL_LP_CNT(x) (x << 8) ++ ++/* Command pointer list entry */ ++#define ADM_CPLE_LP BIT(31) ++#define ADM_CPLE_CMD_PTR_LIST BIT(29) ++ ++/* Command list entry */ ++#define ADM_CMD_LC BIT(31) ++#define ADM_CMD_DST_CRCI(n) (((n) & 0xf) << 7) ++#define ADM_CMD_SRC_CRCI(n) (((n) & 0xf) << 3) ++ ++#define ADM_CMD_TYPE_SINGLE 0x0 ++#define ADM_CMD_TYPE_BOX 0x3 ++ ++#define ADM_CRCI_MUX_SEL BIT(4) ++#define ADM_DESC_ALIGN 8 ++#define ADM_MAX_XFER (SZ_64K-1) ++#define ADM_MAX_ROWS (SZ_64K-1) ++#define ADM_MAX_CHANNELS 16 ++ ++struct adm_desc_hw_box { ++ u32 cmd; ++ u32 src_addr; ++ u32 dst_addr; ++ u32 row_len; ++ u32 num_rows; ++ u32 row_offset; ++}; ++ ++struct adm_desc_hw_single { ++ u32 cmd; ++ u32 src_addr; ++ u32 dst_addr; ++ u32 len; ++}; ++ ++struct adm_async_desc { ++ struct virt_dma_desc vd; ++ struct adm_device *adev; ++ ++ size_t length; ++ enum dma_transfer_direction dir; ++ dma_addr_t dma_addr; ++ size_t dma_len; ++ ++ void *cpl; ++ dma_addr_t cp_addr; ++ u32 crci; ++ u32 mux; ++ u32 blk_size; ++}; ++ ++struct adm_chan { ++ struct virt_dma_chan vc; ++ struct adm_device *adev; ++ ++ /* parsed from DT */ ++ u32 id; /* channel id */ ++ ++ struct adm_async_desc *curr_txd; ++ struct dma_slave_config slave; ++ struct list_head node; ++ ++ int error; ++ int initialized; ++}; ++ ++static inline struct adm_chan *to_adm_chan(struct dma_chan *common) ++{ ++ return container_of(common, struct adm_chan, vc.chan); ++} ++ ++struct adm_device { ++ void __iomem *regs; ++ struct device *dev; ++ struct dma_device common; ++ struct device_dma_parameters dma_parms; ++ struct adm_chan *channels; ++ ++ u32 ee; ++ ++ struct clk *core_clk; ++ struct clk *iface_clk; ++ ++ struct reset_control *clk_reset; ++ struct reset_control *c0_reset; ++ struct reset_control *c1_reset; ++ struct reset_control *c2_reset; ++ int irq; ++}; ++ ++/** ++ * adm_free_chan - Frees dma resources associated with the specific channel ++ * ++ * Free all allocated descriptors associated with this channel ++ * ++ */ ++static void adm_free_chan(struct dma_chan *chan) ++{ ++ /* free all queued descriptors */ ++ vchan_free_chan_resources(to_virt_chan(chan)); ++} ++ ++/** ++ * adm_get_blksize - Get block size from burst value ++ * ++ */ ++static int adm_get_blksize(unsigned int burst) ++{ ++ int ret; ++ ++ switch (burst) { ++ case 16: ++ case 32: ++ case 64: ++ case 128: ++ ret = ffs(burst>>4) - 1; ++ break; ++ case 192: ++ ret = 4; ++ break; ++ case 256: ++ ret = 5; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++/** ++ * adm_process_fc_descriptors - Process descriptors for flow controlled xfers ++ * ++ * @achan: ADM channel ++ * @desc: Descriptor memory pointer ++ * @sg: Scatterlist entry ++ * @crci: CRCI value ++ * @burst: Burst size of transaction ++ * @direction: DMA transfer direction ++ */ ++static void *adm_process_fc_descriptors(struct adm_chan *achan, ++ void *desc, struct scatterlist *sg, u32 crci, u32 burst, ++ enum dma_transfer_direction direction) ++{ ++ struct adm_desc_hw_box *box_desc = NULL; ++ struct adm_desc_hw_single *single_desc; ++ u32 remainder = sg_dma_len(sg); ++ u32 rows, row_offset, crci_cmd; ++ u32 mem_addr = sg_dma_address(sg); ++ u32 *incr_addr = &mem_addr; ++ u32 *src, *dst; ++ ++ if (direction == DMA_DEV_TO_MEM) { ++ crci_cmd = ADM_CMD_SRC_CRCI(crci); ++ row_offset = burst; ++ src = &achan->slave.src_addr; ++ dst = &mem_addr; ++ } else { ++ crci_cmd = ADM_CMD_DST_CRCI(crci); ++ row_offset = burst << 16; ++ src = &mem_addr; ++ dst = &achan->slave.dst_addr; ++ } ++ ++ while (remainder >= burst) { ++ box_desc = desc; ++ box_desc->cmd = ADM_CMD_TYPE_BOX | crci_cmd; ++ box_desc->row_offset = row_offset; ++ box_desc->src_addr = *src; ++ box_desc->dst_addr = *dst; ++ ++ rows = remainder / burst; ++ rows = min_t(u32, rows, ADM_MAX_ROWS); ++ box_desc->num_rows = rows << 16 | rows; ++ box_desc->row_len = burst << 16 | burst; ++ ++ *incr_addr += burst * rows; ++ remainder -= burst * rows; ++ desc += sizeof(*box_desc); ++ } ++ ++ /* if leftover bytes, do one single descriptor */ ++ if (remainder) { ++ single_desc = desc; ++ single_desc->cmd = ADM_CMD_TYPE_SINGLE | crci_cmd; ++ single_desc->len = remainder; ++ single_desc->src_addr = *src; ++ single_desc->dst_addr = *dst; ++ desc += sizeof(*single_desc); ++ ++ if (sg_is_last(sg)) ++ single_desc->cmd |= ADM_CMD_LC; ++ } else { ++ if (box_desc && sg_is_last(sg)) ++ box_desc->cmd |= ADM_CMD_LC; ++ } ++ ++ return desc; ++} ++ ++/** ++ * adm_process_non_fc_descriptors - Process descriptors for non-fc xfers ++ * ++ * @achan: ADM channel ++ * @desc: Descriptor memory pointer ++ * @sg: Scatterlist entry ++ * @direction: DMA transfer direction ++ */ ++static void *adm_process_non_fc_descriptors(struct adm_chan *achan, ++ void *desc, struct scatterlist *sg, ++ enum dma_transfer_direction direction) ++{ ++ struct adm_desc_hw_single *single_desc; ++ u32 remainder = sg_dma_len(sg); ++ u32 mem_addr = sg_dma_address(sg); ++ u32 *incr_addr = &mem_addr; ++ u32 *src, *dst; ++ ++ if (direction == DMA_DEV_TO_MEM) { ++ src = &achan->slave.src_addr; ++ dst = &mem_addr; ++ } else { ++ src = &mem_addr; ++ dst = &achan->slave.dst_addr; ++ } ++ ++ do { ++ single_desc = desc; ++ single_desc->cmd = ADM_CMD_TYPE_SINGLE; ++ single_desc->src_addr = *src; ++ single_desc->dst_addr = *dst; ++ single_desc->len = (remainder > ADM_MAX_XFER) ? ++ ADM_MAX_XFER : remainder; ++ ++ remainder -= single_desc->len; ++ *incr_addr += single_desc->len; ++ desc += sizeof(*single_desc); ++ } while (remainder); ++ ++ /* set last command if this is the end of the whole transaction */ ++ if (sg_is_last(sg)) ++ single_desc->cmd |= ADM_CMD_LC; ++ ++ return desc; ++} ++ ++/** ++ * adm_prep_slave_sg - Prep slave sg transaction ++ * ++ * @chan: dma channel ++ * @sgl: scatter gather list ++ * @sg_len: length of sg ++ * @direction: DMA transfer direction ++ * @flags: DMA flags ++ * @context: transfer context (unused) ++ */ ++static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan, ++ struct scatterlist *sgl, unsigned int sg_len, ++ enum dma_transfer_direction direction, unsigned long flags, ++ void *context) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct adm_device *adev = achan->adev; ++ struct adm_async_desc *async_desc; ++ struct scatterlist *sg; ++ u32 i, burst; ++ u32 single_count = 0, box_count = 0, crci = 0; ++ void *desc; ++ u32 *cple; ++ int blk_size = 0; ++ ++ if (!is_slave_direction(direction)) { ++ dev_err(adev->dev, "invalid dma direction\n"); ++ return NULL; ++ } ++ ++ /* ++ * get burst value from slave configuration ++ */ ++ burst = (direction == DMA_MEM_TO_DEV) ? ++ achan->slave.dst_maxburst : ++ achan->slave.src_maxburst; ++ ++ /* if using flow control, validate burst and crci values */ ++ if (achan->slave.device_fc) { ++ ++ blk_size = adm_get_blksize(burst); ++ if (blk_size < 0) { ++ dev_err(adev->dev, "invalid burst value: %d\n", ++ burst); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ crci = achan->slave.slave_id & 0xf; ++ if (!crci || achan->slave.slave_id > 0x1f) { ++ dev_err(adev->dev, "invalid crci value\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ } ++ ++ /* iterate through sgs and compute allocation size of structures */ ++ for_each_sg(sgl, sg, sg_len, i) { ++ if (achan->slave.device_fc) { ++ box_count += DIV_ROUND_UP(sg_dma_len(sg) / burst, ++ ADM_MAX_ROWS); ++ if (sg_dma_len(sg) % burst) ++ single_count++; ++ } else { ++ single_count += DIV_ROUND_UP(sg_dma_len(sg), ++ ADM_MAX_XFER); ++ } ++ } ++ ++ async_desc = kzalloc(sizeof(*async_desc), GFP_NOWAIT); ++ if (!async_desc) ++ return ERR_PTR(-ENOMEM); ++ ++ if (crci) ++ async_desc->mux = achan->slave.slave_id & ADM_CRCI_MUX_SEL ? ++ ADM_CRCI_CTL_MUX_SEL : 0; ++ async_desc->crci = crci; ++ async_desc->blk_size = blk_size; ++ async_desc->dma_len = single_count * sizeof(struct adm_desc_hw_single) + ++ box_count * sizeof(struct adm_desc_hw_box) + ++ sizeof(*cple) + 2 * ADM_DESC_ALIGN; ++ ++ async_desc->cpl = dma_alloc_writecombine(adev->dev, async_desc->dma_len, ++ &async_desc->dma_addr, GFP_NOWAIT); ++ ++ if (!async_desc->cpl) { ++ kfree(async_desc); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ async_desc->adev = adev; ++ ++ /* both command list entry and descriptors must be 8 byte aligned */ ++ cple = PTR_ALIGN(async_desc->cpl, ADM_DESC_ALIGN); ++ desc = PTR_ALIGN(cple + 1, ADM_DESC_ALIGN); ++ ++ /* init cmd list */ ++ *cple = ADM_CPLE_LP; ++ *cple |= (desc - async_desc->cpl + async_desc->dma_addr) >> 3; ++ ++ for_each_sg(sgl, sg, sg_len, i) { ++ async_desc->length += sg_dma_len(sg); ++ ++ if (achan->slave.device_fc) ++ desc = adm_process_fc_descriptors(achan, desc, sg, crci, ++ burst, direction); ++ else ++ desc = adm_process_non_fc_descriptors(achan, desc, sg, ++ direction); ++ } ++ ++ return vchan_tx_prep(&achan->vc, &async_desc->vd, flags); ++} ++ ++/** ++ * adm_terminate_all - terminate all transactions on a channel ++ * @achan: adm dma channel ++ * ++ * Dequeues and frees all transactions, aborts current transaction ++ * No callbacks are done ++ * ++ */ ++static int adm_terminate_all(struct dma_chan *chan) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct adm_device *adev = achan->adev; ++ unsigned long flags; ++ LIST_HEAD(head); ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ vchan_get_all_descriptors(&achan->vc, &head); ++ ++ /* send flush command to terminate current transaction */ ++ writel_relaxed(0x0, ++ adev->regs + ADM_CH_FLUSH_STATE0(achan->id, adev->ee)); ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ ++ vchan_dma_desc_free_list(&achan->vc, &head); ++ ++ return 0; ++} ++ ++static int adm_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ unsigned long flag; ++ ++ spin_lock_irqsave(&achan->vc.lock, flag); ++ memcpy(&achan->slave, cfg, sizeof(struct dma_slave_config)); ++ spin_unlock_irqrestore(&achan->vc.lock, flag); ++ ++ return 0; ++} ++ ++/** ++ * adm_start_dma - start next transaction ++ * @achan - ADM dma channel ++ */ ++static void adm_start_dma(struct adm_chan *achan) ++{ ++ struct virt_dma_desc *vd = vchan_next_desc(&achan->vc); ++ struct adm_device *adev = achan->adev; ++ struct adm_async_desc *async_desc; ++ ++ lockdep_assert_held(&achan->vc.lock); ++ ++ if (!vd) ++ return; ++ ++ list_del(&vd->node); ++ ++ /* write next command list out to the CMD FIFO */ ++ async_desc = container_of(vd, struct adm_async_desc, vd); ++ achan->curr_txd = async_desc; ++ ++ /* reset channel error */ ++ achan->error = 0; ++ ++ if (!achan->initialized) { ++ /* enable interrupts */ ++ writel(ADM_CH_CONF_SHADOW_EN | ++ ADM_CH_CONF_PERM_MPU_CONF | ++ ADM_CH_CONF_MPU_DISABLE | ++ ADM_CH_CONF_SEC_DOMAIN(adev->ee), ++ adev->regs + ADM_CH_CONF(achan->id)); ++ ++ writel(ADM_CH_RSLT_CONF_IRQ_EN | ADM_CH_RSLT_CONF_FLUSH_EN, ++ adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee)); ++ ++ achan->initialized = 1; ++ } ++ ++ /* set the crci block size if this transaction requires CRCI */ ++ if (async_desc->crci) { ++ writel(async_desc->mux | async_desc->blk_size, ++ adev->regs + ADM_CRCI_CTL(async_desc->crci, adev->ee)); ++ } ++ ++ /* make sure IRQ enable doesn't get reordered */ ++ wmb(); ++ ++ /* write next command list out to the CMD FIFO */ ++ writel(ALIGN(async_desc->dma_addr, ADM_DESC_ALIGN) >> 3, ++ adev->regs + ADM_CH_CMD_PTR(achan->id, adev->ee)); ++} ++ ++/** ++ * adm_dma_irq - irq handler for ADM controller ++ * @irq: IRQ of interrupt ++ * @data: callback data ++ * ++ * IRQ handler for the bam controller ++ */ ++static irqreturn_t adm_dma_irq(int irq, void *data) ++{ ++ struct adm_device *adev = data; ++ u32 srcs, i; ++ struct adm_async_desc *async_desc; ++ unsigned long flags; ++ ++ srcs = readl_relaxed(adev->regs + ++ ADM_SEC_DOMAIN_IRQ_STATUS(adev->ee)); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) { ++ struct adm_chan *achan = &adev->channels[i]; ++ u32 status, result; ++ ++ if (srcs & BIT(i)) { ++ status = readl_relaxed(adev->regs + ++ ADM_CH_STATUS_SD(i, adev->ee)); ++ ++ /* if no result present, skip */ ++ if (!(status & ADM_CH_STATUS_VALID)) ++ continue; ++ ++ result = readl_relaxed(adev->regs + ++ ADM_CH_RSLT(i, adev->ee)); ++ ++ /* no valid results, skip */ ++ if (!(result & ADM_CH_RSLT_VALID)) ++ continue; ++ ++ /* flag error if transaction was flushed or failed */ ++ if (result & (ADM_CH_RSLT_ERR | ADM_CH_RSLT_FLUSH)) ++ achan->error = 1; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ async_desc = achan->curr_txd; ++ ++ achan->curr_txd = NULL; ++ ++ if (async_desc) { ++ vchan_cookie_complete(&async_desc->vd); ++ ++ /* kick off next DMA */ ++ adm_start_dma(achan); ++ } ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ } ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++/** ++ * adm_tx_status - returns status of transaction ++ * @chan: dma channel ++ * @cookie: transaction cookie ++ * @txstate: DMA transaction state ++ * ++ * Return status of dma transaction ++ */ ++static enum dma_status adm_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ++ struct dma_tx_state *txstate) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct virt_dma_desc *vd; ++ enum dma_status ret; ++ unsigned long flags; ++ size_t residue = 0; ++ ++ ret = dma_cookie_status(chan, cookie, txstate); ++ if (ret == DMA_COMPLETE || !txstate) ++ return ret; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ ++ vd = vchan_find_desc(&achan->vc, cookie); ++ if (vd) ++ residue = container_of(vd, struct adm_async_desc, vd)->length; ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ ++ /* ++ * residue is either the full length if it is in the issued list, or 0 ++ * if it is in progress. We have no reliable way of determining ++ * anything inbetween ++ */ ++ dma_set_residue(txstate, residue); ++ ++ if (achan->error) ++ return DMA_ERROR; ++ ++ return ret; ++} ++ ++/** ++ * adm_issue_pending - starts pending transactions ++ * @chan: dma channel ++ * ++ * Issues all pending transactions and starts DMA ++ */ ++static void adm_issue_pending(struct dma_chan *chan) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ ++ if (vchan_issue_pending(&achan->vc) && !achan->curr_txd) ++ adm_start_dma(achan); ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++} ++ ++/** ++ * adm_dma_free_desc - free descriptor memory ++ * @vd: virtual descriptor ++ * ++ */ ++static void adm_dma_free_desc(struct virt_dma_desc *vd) ++{ ++ struct adm_async_desc *async_desc = container_of(vd, ++ struct adm_async_desc, vd); ++ ++ dma_free_writecombine(async_desc->adev->dev, async_desc->dma_len, ++ async_desc->cpl, async_desc->dma_addr); ++ kfree(async_desc); ++} ++ ++static void adm_channel_init(struct adm_device *adev, struct adm_chan *achan, ++ u32 index) ++{ ++ achan->id = index; ++ achan->adev = adev; ++ ++ vchan_init(&achan->vc, &adev->common); ++ achan->vc.desc_free = adm_dma_free_desc; ++} ++ ++static int adm_dma_probe(struct platform_device *pdev) ++{ ++ struct adm_device *adev; ++ struct resource *iores; ++ int ret; ++ u32 i; ++ ++ adev = devm_kzalloc(&pdev->dev, sizeof(*adev), GFP_KERNEL); ++ if (!adev) ++ return -ENOMEM; ++ ++ adev->dev = &pdev->dev; ++ ++ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ adev->regs = devm_ioremap_resource(&pdev->dev, iores); ++ if (IS_ERR(adev->regs)) ++ return PTR_ERR(adev->regs); ++ ++ adev->irq = platform_get_irq(pdev, 0); ++ if (adev->irq < 0) ++ return adev->irq; ++ ++ ret = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &adev->ee); ++ if (ret) { ++ dev_err(adev->dev, "Execution environment unspecified\n"); ++ return ret; ++ } ++ ++ adev->core_clk = devm_clk_get(adev->dev, "core"); ++ if (IS_ERR(adev->core_clk)) ++ return PTR_ERR(adev->core_clk); ++ ++ ret = clk_prepare_enable(adev->core_clk); ++ if (ret) { ++ dev_err(adev->dev, "failed to prepare/enable core clock\n"); ++ return ret; ++ } ++ ++ adev->iface_clk = devm_clk_get(adev->dev, "iface"); ++ if (IS_ERR(adev->iface_clk)) { ++ ret = PTR_ERR(adev->iface_clk); ++ goto err_disable_core_clk; ++ } ++ ++ ret = clk_prepare_enable(adev->iface_clk); ++ if (ret) { ++ dev_err(adev->dev, "failed to prepare/enable iface clock\n"); ++ goto err_disable_core_clk; ++ } ++ ++ adev->clk_reset = devm_reset_control_get(&pdev->dev, "clk"); ++ if (IS_ERR(adev->clk_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 reset\n"); ++ ret = PTR_ERR(adev->clk_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c0_reset = devm_reset_control_get(&pdev->dev, "c0"); ++ if (IS_ERR(adev->c0_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C0 reset\n"); ++ ret = PTR_ERR(adev->c0_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c1_reset = devm_reset_control_get(&pdev->dev, "c1"); ++ if (IS_ERR(adev->c1_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C1 reset\n"); ++ ret = PTR_ERR(adev->c1_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c2_reset = devm_reset_control_get(&pdev->dev, "c2"); ++ if (IS_ERR(adev->c2_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C2 reset\n"); ++ ret = PTR_ERR(adev->c2_reset); ++ goto err_disable_clks; ++ } ++ ++ reset_control_assert(adev->clk_reset); ++ reset_control_assert(adev->c0_reset); ++ reset_control_assert(adev->c1_reset); ++ reset_control_assert(adev->c2_reset); ++ ++ reset_control_deassert(adev->clk_reset); ++ reset_control_deassert(adev->c0_reset); ++ reset_control_deassert(adev->c1_reset); ++ reset_control_deassert(adev->c2_reset); ++ ++ adev->channels = devm_kcalloc(adev->dev, ADM_MAX_CHANNELS, ++ sizeof(*adev->channels), GFP_KERNEL); ++ ++ if (!adev->channels) { ++ ret = -ENOMEM; ++ goto err_disable_clks; ++ } ++ ++ /* allocate and initialize channels */ ++ INIT_LIST_HEAD(&adev->common.channels); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) ++ adm_channel_init(adev, &adev->channels[i], i); ++ ++ /* reset CRCIs */ ++ for (i = 0; i < 16; i++) ++ writel(ADM_CRCI_CTL_RST, adev->regs + ++ ADM_CRCI_CTL(i, adev->ee)); ++ ++ /* configure client interfaces */ ++ writel(ADM_CI_RANGE_START(0x40) | ADM_CI_RANGE_END(0xb0) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(0)); ++ writel(ADM_CI_RANGE_START(0x2a) | ADM_CI_RANGE_END(0x2c) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(1)); ++ writel(ADM_CI_RANGE_START(0x12) | ADM_CI_RANGE_END(0x28) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(2)); ++ writel(ADM_GP_CTL_LP_EN | ADM_GP_CTL_LP_CNT(0xf), ++ adev->regs + ADM_GP_CTL); ++ ++ ret = devm_request_irq(adev->dev, adev->irq, adm_dma_irq, ++ 0, "adm_dma", adev); ++ if (ret) ++ goto err_disable_clks; ++ ++ platform_set_drvdata(pdev, adev); ++ ++ adev->common.dev = adev->dev; ++ adev->common.dev->dma_parms = &adev->dma_parms; ++ ++ /* set capabilities */ ++ dma_cap_zero(adev->common.cap_mask); ++ dma_cap_set(DMA_SLAVE, adev->common.cap_mask); ++ dma_cap_set(DMA_PRIVATE, adev->common.cap_mask); ++ ++ /* initialize dmaengine apis */ ++ adev->common.device_free_chan_resources = adm_free_chan; ++ adev->common.device_prep_slave_sg = adm_prep_slave_sg; ++ adev->common.device_issue_pending = adm_issue_pending; ++ adev->common.device_tx_status = adm_tx_status; ++ adev->common.device_terminate_all = adm_terminate_all; ++ adev->common.device_config = adm_slave_config; ++ ++ ret = dma_async_device_register(&adev->common); ++ if (ret) { ++ dev_err(adev->dev, "failed to register dma async device\n"); ++ goto err_disable_clks; ++ } ++ ++ ret = of_dma_controller_register(pdev->dev.of_node, ++ of_dma_xlate_by_chan_id, ++ &adev->common); ++ if (ret) ++ goto err_unregister_dma; ++ ++ return 0; ++ ++err_unregister_dma: ++ dma_async_device_unregister(&adev->common); ++err_disable_clks: ++ clk_disable_unprepare(adev->iface_clk); ++err_disable_core_clk: ++ clk_disable_unprepare(adev->core_clk); ++ ++ return ret; ++} ++ ++static int adm_dma_remove(struct platform_device *pdev) ++{ ++ struct adm_device *adev = platform_get_drvdata(pdev); ++ struct adm_chan *achan; ++ u32 i; ++ ++ of_dma_controller_free(pdev->dev.of_node); ++ dma_async_device_unregister(&adev->common); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) { ++ achan = &adev->channels[i]; ++ ++ /* mask IRQs for this channel/EE pair */ ++ writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee)); ++ ++ adm_terminate_all(&adev->channels[i].vc.chan); ++ } ++ ++ devm_free_irq(adev->dev, adev->irq, adev); ++ ++ clk_disable_unprepare(adev->core_clk); ++ clk_disable_unprepare(adev->iface_clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id adm_of_match[] = { ++ { .compatible = "qcom,adm", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, adm_of_match); ++ ++static struct platform_driver adm_dma_driver = { ++ .probe = adm_dma_probe, ++ .remove = adm_dma_remove, ++ .driver = { ++ .name = "adm-dma-engine", ++ .of_match_table = adm_of_match, ++ }, ++}; ++ ++module_platform_driver(adm_dma_driver); ++ ++MODULE_AUTHOR("Andy Gross "); ++MODULE_DESCRIPTION("QCOM ADM DMA engine driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/dma/Makefile ++++ b/drivers/dma/Makefile +@@ -49,3 +49,4 @@ obj-y += xilinx/ + obj-$(CONFIG_INTEL_MIC_X100_DMA) += mic_x100_dma.o + obj-$(CONFIG_NBPFAXI_DMA) += nbpfaxi.o + obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o ++obj-$(CONFIG_QCOM_ADM) += qcom_adm.o diff --git a/target/linux/ipq806x/patches-3.18/157-ARM-DT-ipq8064-Add-ADM-device-node.patch b/target/linux/ipq806x/patches-3.18/157-ARM-DT-ipq8064-Add-ADM-device-node.patch new file mode 100644 index 0000000..657e36a --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/157-ARM-DT-ipq8064-Add-ADM-device-node.patch @@ -0,0 +1,42 @@ +From 1fb18acab2d71e7e4efd9c10492edb1baf84dcc0 Mon Sep 17 00:00:00 2001 +From: Andy Gross +Date: Wed, 20 May 2015 15:41:07 +0530 +Subject: [PATCH] ARM: DT: ipq8064: Add ADM device node + +This patch adds support for the ADM DMA on the IPQ8064 SOC + +Signed-off-by: Andy Gross +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 4 ++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 21 +++++++++++++++++++++ + 2 files changed, 25 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -662,6 +662,26 @@ + }; + }; + ++ adm_dma: dma@18300000 { ++ compatible = "qcom,adm"; ++ reg = <0x18300000 0x100000>; ++ interrupts = <0 170 0>; ++ #dma-cells = <1>; ++ ++ clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>; ++ clock-names = "core", "iface"; ++ ++ resets = <&gcc ADM0_RESET>, ++ <&gcc ADM0_PBUS_RESET>, ++ <&gcc ADM0_C0_RESET>, ++ <&gcc ADM0_C1_RESET>, ++ <&gcc ADM0_C2_RESET>; ++ reset-names = "clk", "pbus", "c0", "c1", "c2"; ++ qcom,ee = <0>; ++ ++ status = "disabled"; ++ }; ++ + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-3.18/160-clk-qcom-Add-EBI2-clocks-for-IPQ806x.patch b/target/linux/ipq806x/patches-3.18/160-clk-qcom-Add-EBI2-clocks-for-IPQ806x.patch new file mode 100644 index 0000000..77d29d8 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/160-clk-qcom-Add-EBI2-clocks-for-IPQ806x.patch @@ -0,0 +1,74 @@ +From 4c385b25fab119144bffb255ad77712fe586ac10 Mon Sep 17 00:00:00 2001 +From: Archit Taneja +Date: Thu, 2 Apr 2015 11:20:41 +0530 +Subject: [PATCH] clk: qcom: Add EBI2 clocks for IPQ806x + +The NAND controller within EBI2 requires EBI2_CLK and +EBI2_ALWAYS_ON_CLK clocks. Create structs for these clocks so +that they can be used by the NAND controller driver. Add an entry +for EBI2_AON_CLK in the gcc-ipq806x DT binding document. + +Signed-off-by: Archit Taneja +Signed-off-by: Stephen Boyd +--- + drivers/clk/qcom/gcc-ipq806x.c | 32 ++++++++++++++++++++++++++++ + include/dt-bindings/clock/qcom,gcc-ipq806x.h | 1 + + 2 files changed, 33 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -2239,6 +2239,36 @@ static struct clk_branch usb_fs1_h_clk = + }, + }; + ++static struct clk_branch ebi2_clk = { ++ .hwcg_reg = 0x3b00, ++ .hwcg_bit = 6, ++ .halt_reg = 0x2fcc, ++ .halt_bit = 1, ++ .clkr = { ++ .enable_reg = 0x3b00, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "ebi2_clk", ++ .ops = &clk_branch_ops, ++ .flags = CLK_IS_ROOT, ++ }, ++ }, ++}; ++ ++static struct clk_branch ebi2_aon_clk = { ++ .halt_reg = 0x2fcc, ++ .halt_bit = 0, ++ .clkr = { ++ .enable_reg = 0x3b00, ++ .enable_mask = BIT(8), ++ .hw.init = &(struct clk_init_data){ ++ .name = "ebi2_always_on_clk", ++ .ops = &clk_branch_ops, ++ .flags = CLK_IS_ROOT, ++ }, ++ }, ++}; ++ + static struct clk_regmap *gcc_ipq806x_clks[] = { + [PLL0] = &pll0.clkr, + [PLL0_VOTE] = &pll0_vote, +@@ -2341,6 +2371,8 @@ static struct clk_regmap *gcc_ipq806x_cl + [USB_FS1_XCVR_SRC] = &usb_fs1_xcvr_clk_src.clkr, + [USB_FS1_XCVR_CLK] = &usb_fs1_xcvr_clk.clkr, + [USB_FS1_SYSTEM_CLK] = &usb_fs1_sys_clk.clkr, ++ [EBI2_CLK] = &ebi2_clk.clkr, ++ [EBI2_AON_CLK] = &ebi2_aon_clk.clkr, + [PLL9] = &hfpll0.clkr, + [PLL10] = &hfpll1.clkr, + [PLL12] = &hfpll_l2.clkr, +--- a/include/dt-bindings/clock/qcom,gcc-ipq806x.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq806x.h +@@ -289,5 +289,6 @@ + #define UBI32_CORE2_CLK_SRC 278 + #define UBI32_CORE1_CLK 279 + #define UBI32_CORE2_CLK 280 ++#define EBI2_AON_CLK 281 + + #endif diff --git a/target/linux/ipq806x/patches-3.18/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch b/target/linux/ipq806x/patches-3.18/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch new file mode 100644 index 0000000..3fb4785 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch @@ -0,0 +1,84 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3, + 1/5] mtd: nand: Create a BBT flag to access bad block markers in raw + mode +From: Archit Taneja +X-Patchwork-Id: 6927081 +Message-Id: <1438578498-32254-2-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja +Date: Mon, 3 Aug 2015 10:38:14 +0530 + +Some controllers can access the factory bad block marker from OOB only +when they read it in raw mode. When ECC is enabled, these controllers +discard reading/writing bad block markers, preventing access to them +altogether. + +The bbt driver assumes MTD_OPS_PLACE_OOB when scanning for bad blocks. +This results in the nand driver's ecc->read_oob() op to be called, which +works with ECC enabled. + +Create a new BBT option flag that tells nand_bbt to force the mode to +MTD_OPS_RAW. This would result in the correct op being called for the +underlying nand controller driver. + +Reviewed-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +drivers/mtd/nand/nand_base.c | 6 +++++- + drivers/mtd/nand/nand_bbt.c | 6 +++++- + include/linux/mtd/bbm.h | 7 +++++++ + 3 files changed, 17 insertions(+), 2 deletions(-) + +--- a/drivers/mtd/nand/nand_base.c ++++ b/drivers/mtd/nand/nand_base.c +@@ -396,7 +396,11 @@ static int nand_default_block_markbad(st + } else { + ops.len = ops.ooblen = 1; + } +- ops.mode = MTD_OPS_PLACE_OOB; ++ ++ if (unlikely(chip->bbt_options & NAND_BBT_ACCESS_BBM_RAW)) ++ ops.mode = MTD_OPS_RAW; ++ else ++ ops.mode = MTD_OPS_PLACE_OOB; + + /* Write to first/last page(s) if necessary */ + if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) +--- a/drivers/mtd/nand/nand_bbt.c ++++ b/drivers/mtd/nand/nand_bbt.c +@@ -423,7 +423,11 @@ static int scan_block_fast(struct mtd_in + ops.oobbuf = buf; + ops.ooboffs = 0; + ops.datbuf = NULL; +- ops.mode = MTD_OPS_PLACE_OOB; ++ ++ if (unlikely(bd->options & NAND_BBT_ACCESS_BBM_RAW)) ++ ops.mode = MTD_OPS_RAW; ++ else ++ ops.mode = MTD_OPS_PLACE_OOB; + + for (j = 0; j < numpages; j++) { + /* +--- a/include/linux/mtd/bbm.h ++++ b/include/linux/mtd/bbm.h +@@ -116,6 +116,13 @@ struct nand_bbt_descr { + #define NAND_BBT_NO_OOB_BBM 0x00080000 + + /* ++ * Force MTD_OPS_RAW mode when trying to access bad block markes from OOB. To ++ * be used by controllers which can access BBM only when ECC is disabled, i.e, ++ * when in RAW access mode ++ */ ++#define NAND_BBT_ACCESS_BBM_RAW 0x00100000 ++ ++/* + * Flag set by nand_create_default_bbt_descr(), marking that the nand_bbt_descr + * was allocated dynamicaly and must be freed in nand_release(). Has no meaning + * in nand_chip.bbt_options. diff --git a/target/linux/ipq806x/patches-3.18/162-mtd-nand-Qualcomm-NAND-controller-driver.patch b/target/linux/ipq806x/patches-3.18/162-mtd-nand-Qualcomm-NAND-controller-driver.patch new file mode 100644 index 0000000..6172f7d --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/162-mtd-nand-Qualcomm-NAND-controller-driver.patch @@ -0,0 +1,2024 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,2/5] mtd: nand: Qualcomm NAND controller driver +From: Archit Taneja +X-Patchwork-Id: 6927101 +Message-Id: <1438578498-32254-3-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja +Date: Mon, 3 Aug 2015 10:38:15 +0530 + +The Qualcomm NAND controller is found in SoCs like IPQ806x, MSM7xx, +MDM9x15 series. + +It exists as a sub block inside the IPs EBI2 (External Bus Interface 2) +and QPIC (Qualcomm Parallel Interface Controller). These IPs provide a +broader interface for external slow peripheral devices such as LCD and +NAND/NOR flash memory or SRAM like interfaces. + +We add support for the NAND controller found within EBI2. For the SoCs +of our interest, we only use the NAND controller within EBI2. Therefore, +it's safe for us to assume that the NAND controller is a standalone block +within the SoC. + +The controller supports 512B, 2kB, 4kB and 8kB page 8-bit and 16-bit NAND +flash devices. It contains a HW ECC block that supports BCH ECC (4, 8 and +16 bit correction/step) and RS ECC(4 bit correction/step) that covers main +and spare data. The controller contains an internal 512 byte page buffer +to which we read/write via DMA. The EBI2 type NAND controller uses ADM DMA +for register read/write and data transfers. The controller performs page +reads and writes at a codeword/step level of 512 bytes. It can support up +to 2 external chips of different configurations. + +The driver prepares register read and write configuration descriptors for +each codeword, followed by data descriptors to read or write data from the +controller's internal buffer. It uses a single ADM DMA channel that we get +via dmaengine API. The controller requires 2 ADM CRCIs for command and +data flow control. These are passed via DT. + +The ecc layout used by the controller is syndrome like, but we can't use +the standard syndrome ecc ops because of several reasons. First, the amount +of data bytes covered by ecc isn't same in each step. Second, writing to +free oob space requires us writing to the entire step in which the oob +lies. This forces us to create our own ecc ops. + +One more difference is how the controller accesses the bad block marker. +The controller ignores reading the marker when ECC is enabled. ECC needs +to be explicity disabled to read or write to the bad block marker. For +this reason, we use the newly created flag NAND_BBT_ACCESS_BBM_RAW to +read the factory provided bad block markers. + +v3: +- Refactor dma functions for maximum reuse +- Use dma_slave_confing on stack +- optimize and clean upempty_page_fixup using memchr_inv +- ensure portability with dma register reads using le32_* funcs +- use NAND_USE_BOUNCE_BUFFER instead of doing it ourselves +- fix handling of return values of dmaengine funcs +- constify wherever possible +- Remove dependency on ADM DMA in Kconfig +- Misc fixes and clean ups + +v2: +- Use new BBT flag that allows us to read BBM in raw mode +- reduce memcpy-s in the driver +- some refactor and clean ups because of above changes + +Reviewed-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +drivers/mtd/nand/Kconfig | 7 + + drivers/mtd/nand/Makefile | 1 + + drivers/mtd/nand/qcom_nandc.c | 1913 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 1921 insertions(+) + create mode 100644 drivers/mtd/nand/qcom_nandc.c + +--- a/drivers/mtd/nand/Kconfig ++++ b/drivers/mtd/nand/Kconfig +@@ -516,4 +516,11 @@ config MTD_NAND_XWAY + Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached + to the External Bus Unit (EBU). + ++config MTD_NAND_QCOM ++ tristate "Support for NAND on QCOM SoCs" ++ depends on ARCH_QCOM ++ help ++ Enables support for NAND flash chips on SoCs containing the EBI2 NAND ++ controller. This controller is found on IPQ806x SoC. ++ + endif # MTD_NAND +--- /dev/null ++++ b/drivers/mtd/nand/qcom_nandc.c +@@ -0,0 +1,1918 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* NANDc reg offsets */ ++#define NAND_FLASH_CMD 0x00 ++#define NAND_ADDR0 0x04 ++#define NAND_ADDR1 0x08 ++#define NAND_FLASH_CHIP_SELECT 0x0c ++#define NAND_EXEC_CMD 0x10 ++#define NAND_FLASH_STATUS 0x14 ++#define NAND_BUFFER_STATUS 0x18 ++#define NAND_DEV0_CFG0 0x20 ++#define NAND_DEV0_CFG1 0x24 ++#define NAND_DEV0_ECC_CFG 0x28 ++#define NAND_DEV1_ECC_CFG 0x2c ++#define NAND_DEV1_CFG0 0x30 ++#define NAND_DEV1_CFG1 0x34 ++#define NAND_READ_ID 0x40 ++#define NAND_READ_STATUS 0x44 ++#define NAND_DEV_CMD0 0xa0 ++#define NAND_DEV_CMD1 0xa4 ++#define NAND_DEV_CMD2 0xa8 ++#define NAND_DEV_CMD_VLD 0xac ++#define SFLASHC_BURST_CFG 0xe0 ++#define NAND_ERASED_CW_DETECT_CFG 0xe8 ++#define NAND_ERASED_CW_DETECT_STATUS 0xec ++#define NAND_EBI2_ECC_BUF_CFG 0xf0 ++#define FLASH_BUF_ACC 0x100 ++ ++#define NAND_CTRL 0xf00 ++#define NAND_VERSION 0xf08 ++#define NAND_READ_LOCATION_0 0xf20 ++#define NAND_READ_LOCATION_1 0xf24 ++ ++/* dummy register offsets, used by write_reg_dma */ ++#define NAND_DEV_CMD1_RESTORE 0xdead ++#define NAND_DEV_CMD_VLD_RESTORE 0xbeef ++ ++/* NAND_FLASH_CMD bits */ ++#define PAGE_ACC BIT(4) ++#define LAST_PAGE BIT(5) ++ ++/* NAND_FLASH_CHIP_SELECT bits */ ++#define NAND_DEV_SEL 0 ++#define DM_EN BIT(2) ++ ++/* NAND_FLASH_STATUS bits */ ++#define FS_OP_ERR BIT(4) ++#define FS_READY_BSY_N BIT(5) ++#define FS_MPU_ERR BIT(8) ++#define FS_DEVICE_STS_ERR BIT(16) ++#define FS_DEVICE_WP BIT(23) ++ ++/* NAND_BUFFER_STATUS bits */ ++#define BS_UNCORRECTABLE_BIT BIT(8) ++#define BS_CORRECTABLE_ERR_MSK 0x1f ++ ++/* NAND_DEVn_CFG0 bits */ ++#define DISABLE_STATUS_AFTER_WRITE 4 ++#define CW_PER_PAGE 6 ++#define UD_SIZE_BYTES 9 ++#define ECC_PARITY_SIZE_BYTES_RS 19 ++#define SPARE_SIZE_BYTES 23 ++#define NUM_ADDR_CYCLES 27 ++#define STATUS_BFR_READ 30 ++#define SET_RD_MODE_AFTER_STATUS 31 ++ ++/* NAND_DEVn_CFG0 bits */ ++#define DEV0_CFG1_ECC_DISABLE 0 ++#define WIDE_FLASH 1 ++#define NAND_RECOVERY_CYCLES 2 ++#define CS_ACTIVE_BSY 5 ++#define BAD_BLOCK_BYTE_NUM 6 ++#define BAD_BLOCK_IN_SPARE_AREA 16 ++#define WR_RD_BSY_GAP 17 ++#define ENABLE_BCH_ECC 27 ++ ++/* NAND_DEV0_ECC_CFG bits */ ++#define ECC_CFG_ECC_DISABLE 0 ++#define ECC_SW_RESET 1 ++#define ECC_MODE 4 ++#define ECC_PARITY_SIZE_BYTES_BCH 8 ++#define ECC_NUM_DATA_BYTES 16 ++#define ECC_FORCE_CLK_OPEN 30 ++ ++/* NAND_DEV_CMD1 bits */ ++#define READ_ADDR 0 ++ ++/* NAND_DEV_CMD_VLD bits */ ++#define READ_START_VLD 0 ++ ++/* NAND_EBI2_ECC_BUF_CFG bits */ ++#define NUM_STEPS 0 ++ ++/* NAND_ERASED_CW_DETECT_CFG bits */ ++#define ERASED_CW_ECC_MASK 1 ++#define AUTO_DETECT_RES 0 ++#define MASK_ECC (1 << ERASED_CW_ECC_MASK) ++#define RESET_ERASED_DET (1 << AUTO_DETECT_RES) ++#define ACTIVE_ERASED_DET (0 << AUTO_DETECT_RES) ++#define CLR_ERASED_PAGE_DET (RESET_ERASED_DET | MASK_ECC) ++#define SET_ERASED_PAGE_DET (ACTIVE_ERASED_DET | MASK_ECC) ++ ++/* NAND_ERASED_CW_DETECT_STATUS bits */ ++#define PAGE_ALL_ERASED BIT(7) ++#define CODEWORD_ALL_ERASED BIT(6) ++#define PAGE_ERASED BIT(5) ++#define CODEWORD_ERASED BIT(4) ++#define ERASED_PAGE (PAGE_ALL_ERASED | PAGE_ERASED) ++#define ERASED_CW (CODEWORD_ALL_ERASED | CODEWORD_ERASED) ++ ++/* Version Mask */ ++#define NAND_VERSION_MAJOR_MASK 0xf0000000 ++#define NAND_VERSION_MAJOR_SHIFT 28 ++#define NAND_VERSION_MINOR_MASK 0x0fff0000 ++#define NAND_VERSION_MINOR_SHIFT 16 ++ ++/* NAND OP_CMDs */ ++#define PAGE_READ 0x2 ++#define PAGE_READ_WITH_ECC 0x3 ++#define PAGE_READ_WITH_ECC_SPARE 0x4 ++#define PROGRAM_PAGE 0x6 ++#define PAGE_PROGRAM_WITH_ECC 0x7 ++#define PROGRAM_PAGE_SPARE 0x9 ++#define BLOCK_ERASE 0xa ++#define FETCH_ID 0xb ++#define RESET_DEVICE 0xd ++ ++/* ++ * the NAND controller performs reads/writes with ECC in 516 byte chunks. ++ * the driver calls the chunks 'step' or 'codeword' interchangeably ++ */ ++#define NANDC_STEP_SIZE 512 ++ ++/* ++ * the largest page size we support is 8K, this will have 16 steps/codewords ++ * of 512 bytes each ++ */ ++#define MAX_NUM_STEPS (SZ_8K / NANDC_STEP_SIZE) ++ ++/* we read at most 3 registers per codeword scan */ ++#define MAX_REG_RD (3 * MAX_NUM_STEPS) ++ ++/* ECC modes */ ++#define ECC_NONE BIT(0) ++#define ECC_RS_4BIT BIT(1) ++#define ECC_BCH_4BIT BIT(2) ++#define ECC_BCH_8BIT BIT(3) ++ ++struct desc_info { ++ struct list_head list; ++ ++ enum dma_transfer_direction dir; ++ struct scatterlist sgl; ++ struct dma_async_tx_descriptor *dma_desc; ++}; ++ ++/* ++ * holds the current register values that we want to write. acts as a contiguous ++ * chunk of memory which we use to write the controller registers through DMA. ++ */ ++struct nandc_regs { ++ u32 cmd; ++ u32 addr0; ++ u32 addr1; ++ u32 chip_sel; ++ u32 exec; ++ ++ u32 cfg0; ++ u32 cfg1; ++ u32 ecc_bch_cfg; ++ ++ u32 clrflashstatus; ++ u32 clrreadstatus; ++ ++ u32 cmd1; ++ u32 vld; ++ ++ u32 orig_cmd1; ++ u32 orig_vld; ++ ++ u32 ecc_buf_cfg; ++}; ++ ++/* ++ * @cmd_crci: ADM DMA CRCI for command flow control ++ * @data_crci: ADM DMA CRCI for data flow control ++ * @list: DMA descriptor list (list of desc_infos) ++ * @dma_done: completion param to denote end of last ++ * descriptor in the list ++ * @data_buffer: our local DMA buffer for page read/writes, ++ * used when we can't use the buffer provided ++ * by upper layers directly ++ * @buf_size/count/start: markers for chip->read_buf/write_buf functions ++ * @reg_read_buf: buffer for reading register data via DMA ++ * @reg_read_pos: marker for data read in reg_read_buf ++ * @cfg0, cfg1, cfg0_raw..: NANDc register configurations needed for ++ * ecc/non-ecc mode for the current nand flash ++ * device ++ * @regs: a contiguous chunk of memory for DMA register ++ * writes ++ * @ecc_strength: 4 bit or 8 bit ecc, received via DT ++ * @bus_width: 8 bit or 16 bit NAND bus width, received via DT ++ * @ecc_modes: supported ECC modes by the current controller, ++ * initialized via DT match data ++ * @cw_size: the number of bytes in a single step/codeword ++ * of a page, consisting of all data, ecc, spare ++ * and reserved bytes ++ * @cw_data: the number of bytes within a codeword protected ++ * by ECC ++ * @bch_enabled: flag to tell whether BCH or RS ECC mode is used ++ * @status: value to be returned if NAND_CMD_STATUS command ++ * is executed ++ */ ++struct qcom_nandc_data { ++ struct platform_device *pdev; ++ struct device *dev; ++ ++ void __iomem *base; ++ struct resource *res; ++ ++ struct clk *core_clk; ++ struct clk *aon_clk; ++ ++ /* DMA stuff */ ++ struct dma_chan *chan; ++ struct dma_slave_config slave_conf; ++ unsigned int cmd_crci; ++ unsigned int data_crci; ++ struct list_head list; ++ struct completion dma_done; ++ ++ /* MTD stuff */ ++ struct nand_chip chip; ++ struct mtd_info mtd; ++ ++ /* local data buffer and markers */ ++ u8 *data_buffer; ++ int buf_size; ++ int buf_count; ++ int buf_start; ++ ++ /* local buffer to read back registers */ ++ u32 *reg_read_buf; ++ int reg_read_pos; ++ ++ /* required configs */ ++ u32 cfg0, cfg1; ++ u32 cfg0_raw, cfg1_raw; ++ u32 ecc_buf_cfg; ++ u32 ecc_bch_cfg; ++ u32 clrflashstatus; ++ u32 clrreadstatus; ++ u32 sflashc_burst_cfg; ++ u32 cmd1, vld; ++ ++ /* register state */ ++ struct nandc_regs *regs; ++ ++ /* things we get from DT */ ++ int ecc_strength; ++ int bus_width; ++ ++ u32 ecc_modes; ++ ++ /* misc params */ ++ int cw_size; ++ int cw_data; ++ bool use_ecc; ++ bool bch_enabled; ++ u8 status; ++ int last_command; ++}; ++ ++static inline u32 nandc_read(struct qcom_nandc_data *this, int offset) ++{ ++ return ioread32(this->base + offset); ++} ++ ++static inline void nandc_write(struct qcom_nandc_data *this, int offset, ++ u32 val) ++{ ++ iowrite32(val, this->base + offset); ++} ++ ++/* helper to configure address register values */ ++static void set_address(struct qcom_nandc_data *this, u16 column, int page) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nandc_regs *regs = this->regs; ++ ++ if (chip->options & NAND_BUSWIDTH_16) ++ column >>= 1; ++ ++ regs->addr0 = page << 16 | column; ++ regs->addr1 = page >> 16 & 0xff; ++} ++ ++/* ++ * update_rw_regs: set up read/write register values, these will be ++ * written to the NAND controller registers via DMA ++ * ++ * @num_cw: number of steps for the read/write operation ++ * @read: read or write operation ++ */ ++static void update_rw_regs(struct qcom_nandc_data *this, int num_cw, bool read) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ if (read) { ++ if (this->use_ecc) ++ regs->cmd = PAGE_READ_WITH_ECC | PAGE_ACC | LAST_PAGE; ++ else ++ regs->cmd = PAGE_READ | PAGE_ACC | LAST_PAGE; ++ } else { ++ regs->cmd = PROGRAM_PAGE | PAGE_ACC | LAST_PAGE; ++ } ++ ++ if (this->use_ecc) { ++ regs->cfg0 = (this->cfg0 & ~(7U << CW_PER_PAGE)) | ++ (num_cw - 1) << CW_PER_PAGE; ++ ++ regs->cfg1 = this->cfg1; ++ regs->ecc_bch_cfg = this->ecc_bch_cfg; ++ } else { ++ regs->cfg0 = (this->cfg0_raw & ~(7U << CW_PER_PAGE)) | ++ (num_cw - 1) << CW_PER_PAGE; ++ ++ regs->cfg1 = this->cfg1_raw; ++ regs->ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE; ++ } ++ ++ regs->ecc_buf_cfg = this->ecc_buf_cfg; ++ regs->clrflashstatus = this->clrflashstatus; ++ regs->clrreadstatus = this->clrreadstatus; ++ regs->exec = 1; ++} ++ ++static int prep_dma_desc(struct qcom_nandc_data *this, bool read, int reg_off, ++ const void *vaddr, int size, bool flow_control) ++{ ++ struct desc_info *desc; ++ struct dma_async_tx_descriptor *dma_desc; ++ struct scatterlist *sgl; ++ struct dma_slave_config slave_conf; ++ int r; ++ ++ desc = kzalloc(sizeof(*desc), GFP_KERNEL); ++ if (!desc) ++ return -ENOMEM; ++ ++ list_add_tail(&desc->list, &this->list); ++ ++ sgl = &desc->sgl; ++ ++ sg_init_one(sgl, vaddr, size); ++ ++ desc->dir = read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; ++ ++ r = dma_map_sg(this->dev, sgl, 1, desc->dir); ++ if (r == 0) { ++ r = -ENOMEM; ++ goto err; ++ } ++ ++ memset(&slave_conf, 0x00, sizeof(slave_conf)); ++ ++ slave_conf.device_fc = flow_control; ++ if (read) { ++ slave_conf.src_maxburst = 16; ++ slave_conf.src_addr = this->res->start + reg_off; ++ slave_conf.slave_id = this->data_crci; ++ } else { ++ slave_conf.dst_maxburst = 16; ++ slave_conf.dst_addr = this->res->start + reg_off; ++ slave_conf.slave_id = this->cmd_crci; ++ } ++ ++ r = dmaengine_slave_config(this->chan, &slave_conf); ++ if (r) { ++ dev_err(this->dev, "failed to configure dma channel\n"); ++ goto err; ++ } ++ ++ dma_desc = dmaengine_prep_slave_sg(this->chan, sgl, 1, desc->dir, 0); ++ if (!dma_desc) { ++ dev_err(this->dev, "failed to prepare desc\n"); ++ r = -EINVAL; ++ goto err; ++ } ++ ++ desc->dma_desc = dma_desc; ++ ++ return 0; ++err: ++ kfree(desc); ++ ++ return r; ++} ++ ++/* ++ * read_reg_dma: prepares a descriptor to read a given number of ++ * contiguous registers to the reg_read_buf pointer ++ * ++ * @first: offset of the first register in the contiguous block ++ * @num_regs: number of registers to read ++ */ ++static int read_reg_dma(struct qcom_nandc_data *this, int first, int num_regs) ++{ ++ bool flow_control = false; ++ void *vaddr; ++ int size; ++ ++ if (first == NAND_READ_ID || first == NAND_FLASH_STATUS) ++ flow_control = true; ++ ++ size = num_regs * sizeof(u32); ++ vaddr = this->reg_read_buf + this->reg_read_pos; ++ this->reg_read_pos += num_regs; ++ ++ return prep_dma_desc(this, true, first, vaddr, size, flow_control); ++} ++ ++/* ++ * write_reg_dma: prepares a descriptor to write a given number of ++ * contiguous registers ++ * ++ * @first: offset of the first register in the contiguous block ++ * @num_regs: number of registers to write ++ */ ++static int write_reg_dma(struct qcom_nandc_data *this, int first, int num_regs) ++{ ++ bool flow_control = false; ++ struct nandc_regs *regs = this->regs; ++ void *vaddr; ++ int size; ++ ++ switch (first) { ++ case NAND_FLASH_CMD: ++ vaddr = ®s->cmd; ++ flow_control = true; ++ break; ++ case NAND_EXEC_CMD: ++ vaddr = ®s->exec; ++ break; ++ case NAND_FLASH_STATUS: ++ vaddr = ®s->clrflashstatus; ++ break; ++ case NAND_DEV0_CFG0: ++ vaddr = ®s->cfg0; ++ break; ++ case NAND_READ_STATUS: ++ vaddr = ®s->clrreadstatus; ++ break; ++ case NAND_DEV_CMD1: ++ vaddr = ®s->cmd1; ++ break; ++ case NAND_DEV_CMD1_RESTORE: ++ first = NAND_DEV_CMD1; ++ vaddr = ®s->orig_cmd1; ++ break; ++ case NAND_DEV_CMD_VLD: ++ vaddr = ®s->vld; ++ break; ++ case NAND_DEV_CMD_VLD_RESTORE: ++ first = NAND_DEV_CMD_VLD; ++ vaddr = ®s->orig_vld; ++ break; ++ case NAND_EBI2_ECC_BUF_CFG: ++ vaddr = ®s->ecc_buf_cfg; ++ break; ++ default: ++ dev_err(this->dev, "invalid starting register\n"); ++ return -EINVAL; ++ } ++ ++ size = num_regs * sizeof(u32); ++ ++ return prep_dma_desc(this, false, first, vaddr, size, flow_control); ++} ++ ++/* ++ * read_data_dma: prepares a DMA descriptor to transfer data from the ++ * controller's internal buffer to the buffer 'vaddr' ++ * ++ * @reg_off: offset within the controller's data buffer ++ * @vaddr: virtual address of the buffer we want to write to ++ * @size: DMA transaction size in bytes ++ */ ++static int read_data_dma(struct qcom_nandc_data *this, int reg_off, ++ const u8 *vaddr, int size) ++{ ++ return prep_dma_desc(this, true, reg_off, vaddr, size, false); ++} ++ ++/* ++ * write_data_dma: prepares a DMA descriptor to transfer data from ++ * 'vaddr' to the controller's internal buffer ++ * ++ * @reg_off: offset within the controller's data buffer ++ * @vaddr: virtual address of the buffer we want to read from ++ * @size: DMA transaction size in bytes ++ */ ++static int write_data_dma(struct qcom_nandc_data *this, int reg_off, ++ const u8 *vaddr, int size) ++{ ++ return prep_dma_desc(this, false, reg_off, vaddr, size, false); ++} ++ ++/* ++ * helper to prepare dma descriptors to configure registers needed for reading a ++ * codeword/step in a page ++ */ ++static void config_cw_read(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 3); ++ write_reg_dma(this, NAND_EBI2_ECC_BUF_CFG, 1); ++ ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 2); ++ read_reg_dma(this, NAND_ERASED_CW_DETECT_STATUS, 1); ++} ++ ++/* ++ * helpers to prepare dma descriptors used to configure registers needed for ++ * writing a codeword/step in a page ++ */ ++static void config_cw_write_pre(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 3); ++ write_reg_dma(this, NAND_EBI2_ECC_BUF_CFG, 1); ++} ++ ++static void config_cw_write_post(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ write_reg_dma(this, NAND_FLASH_STATUS, 1); ++ write_reg_dma(this, NAND_READ_STATUS, 1); ++} ++ ++/* ++ * the following functions are used within chip->cmdfunc() to perform different ++ * NAND_CMD_* commands ++ */ ++ ++/* sets up descriptors for NAND_CMD_PARAM */ ++static int nandc_param(struct qcom_nandc_data *this) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ /* ++ * NAND_CMD_PARAM is called before we know much about the FLASH chip ++ * in use. we configure the controller to perform a raw read of 512 ++ * bytes to read onfi params ++ */ ++ regs->cmd = PAGE_READ | PAGE_ACC | LAST_PAGE; ++ regs->addr0 = 0; ++ regs->addr1 = 0; ++ regs->cfg0 = 0 << CW_PER_PAGE ++ | 512 << UD_SIZE_BYTES ++ | 5 << NUM_ADDR_CYCLES ++ | 0 << SPARE_SIZE_BYTES; ++ ++ regs->cfg1 = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | 17 << BAD_BLOCK_BYTE_NUM ++ | 1 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | 0 << WIDE_FLASH ++ | 1 << DEV0_CFG1_ECC_DISABLE; ++ ++ regs->ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE; ++ ++ /* configure CMD1 and VLD for ONFI param probing */ ++ regs->vld = (this->vld & ~(1 << READ_START_VLD)) ++ | 0 << READ_START_VLD; ++ ++ regs->cmd1 = (this->cmd1 & ~(0xFF << READ_ADDR)) ++ | NAND_CMD_PARAM << READ_ADDR; ++ ++ regs->exec = 1; ++ ++ regs->orig_cmd1 = this->cmd1; ++ regs->orig_vld = this->vld; ++ ++ write_reg_dma(this, NAND_DEV_CMD_VLD, 1); ++ write_reg_dma(this, NAND_DEV_CMD1, 1); ++ ++ this->buf_count = 512; ++ memset(this->data_buffer, 0xff, this->buf_count); ++ ++ config_cw_read(this); ++ ++ read_data_dma(this, FLASH_BUF_ACC, this->data_buffer, this->buf_count); ++ ++ /* restore CMD1 and VLD regs */ ++ write_reg_dma(this, NAND_DEV_CMD1_RESTORE, 1); ++ write_reg_dma(this, NAND_DEV_CMD_VLD_RESTORE, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_ERASE1 */ ++static int erase_block(struct qcom_nandc_data *this, int page_addr) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ regs->cmd = BLOCK_ERASE | PAGE_ACC | LAST_PAGE; ++ regs->addr0 = page_addr; ++ regs->addr1 = 0; ++ regs->cfg0 = this->cfg0_raw & ~(7 << CW_PER_PAGE); ++ regs->cfg1 = this->cfg1_raw; ++ regs->exec = 1; ++ regs->clrflashstatus = this->clrflashstatus; ++ regs->clrreadstatus = this->clrreadstatus; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 2); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ write_reg_dma(this, NAND_FLASH_STATUS, 1); ++ write_reg_dma(this, NAND_READ_STATUS, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_READID */ ++static int read_id(struct qcom_nandc_data *this, int column) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ if (column == -1) ++ return 0; ++ ++ regs->cmd = FETCH_ID; ++ regs->addr0 = column; ++ regs->addr1 = 0; ++ regs->chip_sel = DM_EN; ++ regs->exec = 1; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 4); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_READ_ID, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_RESET */ ++static int reset(struct qcom_nandc_data *this) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ regs->cmd = RESET_DEVICE; ++ regs->exec = 1; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 1); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ return 0; ++} ++ ++/* helpers to submit/free our list of dma descriptors */ ++static void dma_callback(void *param) ++{ ++ struct qcom_nandc_data *this = param; ++ struct completion *c = &this->dma_done; ++ ++ complete(c); ++} ++ ++static int submit_descs(struct qcom_nandc_data *this) ++{ ++ struct completion *c = &this->dma_done; ++ struct desc_info *desc; ++ int r; ++ ++ init_completion(c); ++ ++ list_for_each_entry(desc, &this->list, list) { ++ /* ++ * we add a callback to the last descriptor in our list to ++ * notify completion of command ++ */ ++ if (list_is_last(&desc->list, &this->list)) { ++ desc->dma_desc->callback = dma_callback; ++ desc->dma_desc->callback_param = this; ++ } ++ ++ dmaengine_submit(desc->dma_desc); ++ } ++ ++ dma_async_issue_pending(this->chan); ++ ++ r = wait_for_completion_timeout(c, msecs_to_jiffies(500)); ++ if (!r) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static void free_descs(struct qcom_nandc_data *this) ++{ ++ struct desc_info *desc, *n; ++ ++ list_for_each_entry_safe(desc, n, &this->list, list) { ++ list_del(&desc->list); ++ dma_unmap_sg(this->dev, &desc->sgl, 1, desc->dir); ++ kfree(desc); ++ } ++} ++ ++/* reset the register read buffer for next NAND operation */ ++static void clear_read_regs(struct qcom_nandc_data *this) ++{ ++ this->reg_read_pos = 0; ++ memset(this->reg_read_buf, 0, MAX_REG_RD * sizeof(*this->reg_read_buf)); ++} ++ ++static void pre_command(struct qcom_nandc_data *this, int command) ++{ ++ this->buf_count = 0; ++ this->buf_start = 0; ++ this->use_ecc = false; ++ this->last_command = command; ++ ++ clear_read_regs(this); ++} ++ ++/* ++ * this is called after NAND_CMD_PAGEPROG and NAND_CMD_ERASE1 to set our ++ * privately maintained status byte, this status byte can be read after ++ * NAND_CMD_STATUS is called ++ */ ++static void parse_erase_write_errors(struct qcom_nandc_data *this, int command) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int num_cw; ++ int i; ++ ++ num_cw = command == NAND_CMD_PAGEPROG ? ecc->steps : 1; ++ ++ for (i = 0; i < num_cw; i++) { ++ __le32 flash_status = le32_to_cpu(this->reg_read_buf[i]); ++ ++ if (flash_status & FS_MPU_ERR) ++ this->status &= ~NAND_STATUS_WP; ++ ++ if (flash_status & FS_OP_ERR || (i == (num_cw - 1) && ++ (flash_status & FS_DEVICE_STS_ERR))) ++ this->status |= NAND_STATUS_FAIL; ++ } ++} ++ ++static void post_command(struct qcom_nandc_data *this, int command) ++{ ++ switch (command) { ++ case NAND_CMD_READID: ++ memcpy(this->data_buffer, this->reg_read_buf, this->buf_count); ++ break; ++ case NAND_CMD_PAGEPROG: ++ case NAND_CMD_ERASE1: ++ parse_erase_write_errors(this, command); ++ break; ++ default: ++ break; ++ } ++} ++ ++/* ++ * Implements chip->cmdfunc. It's only used for a limited set of commands. ++ * The rest of the commands wouldn't be called by upper layers. For example, ++ * NAND_CMD_READOOB would never be called because we have our own versions ++ * of read_oob ops for nand_ecc_ctrl. ++ */ ++static void qcom_nandc_command(struct mtd_info *mtd, unsigned int command, ++ int column, int page_addr) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ struct qcom_nandc_data *this = chip->priv; ++ bool wait = false; ++ int r = 0; ++ ++ pre_command(this, command); ++ ++ switch (command) { ++ case NAND_CMD_RESET: ++ r = reset(this); ++ wait = true; ++ break; ++ ++ case NAND_CMD_READID: ++ this->buf_count = 4; ++ r = read_id(this, column); ++ wait = true; ++ break; ++ ++ case NAND_CMD_PARAM: ++ r = nandc_param(this); ++ wait = true; ++ break; ++ ++ case NAND_CMD_ERASE1: ++ r = erase_block(this, page_addr); ++ wait = true; ++ break; ++ ++ case NAND_CMD_READ0: ++ /* we read the entire page for now */ ++ WARN_ON(column != 0); ++ ++ this->use_ecc = true; ++ set_address(this, 0, page_addr); ++ update_rw_regs(this, ecc->steps, true); ++ break; ++ ++ case NAND_CMD_SEQIN: ++ WARN_ON(column != 0); ++ set_address(this, 0, page_addr); ++ break; ++ ++ case NAND_CMD_PAGEPROG: ++ case NAND_CMD_STATUS: ++ case NAND_CMD_NONE: ++ default: ++ break; ++ } ++ ++ if (r) { ++ dev_err(this->dev, "failure executing command %d\n", ++ command); ++ free_descs(this); ++ return; ++ } ++ ++ if (wait) { ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, ++ "failure submitting descs for command %d\n", ++ command); ++ } ++ ++ free_descs(this); ++ ++ post_command(this, command); ++} ++ ++/* ++ * when using RS ECC, the NAND controller flags an error when reading an ++ * erased page. however, there are special characters at certain offsets when ++ * we read the erased page. we check here if the page is really empty. if so, ++ * we replace the magic characters with 0xffs ++ */ ++static bool empty_page_fixup(struct qcom_nandc_data *this, u8 *data_buf) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = ecc->steps; ++ u8 orig1[MAX_NUM_STEPS], orig2[MAX_NUM_STEPS]; ++ int i, j; ++ ++ /* if BCH is enabled, HW will take care of detecting erased pages */ ++ if (this->bch_enabled || !this->use_ecc) ++ return false; ++ ++ for (i = 0; i < cwperpage; i++) { ++ u8 *empty1, *empty2; ++ __le32 flash_status = le32_to_cpu(this->reg_read_buf[3 * i]); ++ ++ /* ++ * an erased page flags an error in NAND_FLASH_STATUS, check if ++ * the page is erased by looking for 0x54s at offsets 3 and 175 ++ * from the beginning of each codeword ++ */ ++ if (!(flash_status & FS_OP_ERR)) ++ break; ++ ++ empty1 = &data_buf[3 + i * this->cw_data]; ++ empty2 = &data_buf[175 + i * this->cw_data]; ++ ++ /* ++ * if the error wasn't because of an erased page, bail out and ++ * and let someone else do the error checking ++ */ ++ if ((*empty1 == 0x54 && *empty2 == 0xff) || ++ (*empty1 == 0xff && *empty2 == 0x54)) { ++ orig1[i] = *empty1; ++ orig2[i] = *empty2; ++ ++ *empty1 = 0xff; ++ *empty2 = 0xff; ++ } else { ++ break; ++ } ++ } ++ ++ if (i < cwperpage || memchr_inv(data_buf, 0xff, mtd->writesize)) ++ goto not_empty; ++ ++ /* ++ * tell the caller that the page was empty and is fixed up, so that ++ * parse_read_errors() doesn't think it's an error ++ */ ++ return true; ++ ++not_empty: ++ /* restore original values if not empty*/ ++ for (j = 0; j < i; j++) { ++ data_buf[3 + j * this->cw_data] = orig1[j]; ++ data_buf[175 + j * this->cw_data] = orig2[j]; ++ } ++ ++ return false; ++} ++ ++struct read_stats { ++ __le32 flash; ++ __le32 buffer; ++ __le32 erased_cw; ++}; ++ ++/* ++ * reads back status registers set by the controller to notify page read ++ * errors. this is equivalent to what 'ecc->correct()' would do. ++ */ ++static int parse_read_errors(struct qcom_nandc_data *this, bool erased_page) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = ecc->steps; ++ unsigned int max_bitflips = 0; ++ int i; ++ ++ for (i = 0; i < cwperpage; i++) { ++ int stat; ++ struct read_stats *buf; ++ ++ buf = (struct read_stats *) (this->reg_read_buf + 3 * i); ++ ++ buf->flash = le32_to_cpu(buf->flash); ++ buf->buffer = le32_to_cpu(buf->buffer); ++ buf->erased_cw = le32_to_cpu(buf->erased_cw); ++ ++ if (buf->flash & (FS_OP_ERR | FS_MPU_ERR)) { ++ ++ /* ignore erased codeword errors */ ++ if (this->bch_enabled) { ++ if ((buf->erased_cw & ERASED_CW) == ERASED_CW) ++ continue; ++ } else if (erased_page) { ++ continue; ++ } ++ ++ if (buf->buffer & BS_UNCORRECTABLE_BIT) { ++ mtd->ecc_stats.failed++; ++ continue; ++ } ++ } ++ ++ stat = buf->buffer & BS_CORRECTABLE_ERR_MSK; ++ mtd->ecc_stats.corrected += stat; ++ ++ max_bitflips = max_t(unsigned int, max_bitflips, stat); ++ } ++ ++ return max_bitflips; ++} ++ ++/* ++ * helper to perform the actual page read operation, used by ecc->read_page() ++ * and ecc->read_oob() ++ */ ++static int read_page_low(struct qcom_nandc_data *this, u8 *data_buf, ++ u8 *oob_buf) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int i, r; ++ ++ /* queue cmd descs for each codeword */ ++ for (i = 0; i < ecc->steps; i++) { ++ int data_size, oob_size; ++ ++ if (i == (ecc->steps - 1)) { ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ } else { ++ data_size = this->cw_data; ++ oob_size = ecc->bytes; ++ } ++ ++ config_cw_read(this); ++ ++ if (data_buf) ++ read_data_dma(this, FLASH_BUF_ACC, data_buf, data_size); ++ ++ if (oob_buf) ++ read_data_dma(this, FLASH_BUF_ACC + data_size, oob_buf, ++ oob_size); ++ ++ if (data_buf) ++ data_buf += data_size; ++ if (oob_buf) ++ oob_buf += oob_size; ++ } ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failure to read page/oob\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* ++ * a helper that copies the last step/codeword of a page (containing free oob) ++ * into our local buffer ++ */ ++static int copy_last_cw(struct qcom_nandc_data *this, bool use_ecc, int page) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int size; ++ int r; ++ ++ clear_read_regs(this); ++ ++ size = use_ecc ? this->cw_data : this->cw_size; ++ ++ /* prepare a clean read buffer */ ++ memset(this->data_buffer, 0xff, size); ++ ++ this->use_ecc = use_ecc; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, true); ++ ++ config_cw_read(this); ++ ++ read_data_dma(this, FLASH_BUF_ACC, this->data_buffer, size); ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failed to copy last codeword\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* implements ecc->read_page() */ ++static int qcom_nandc_read_page(struct mtd_info *mtd, struct nand_chip *chip, ++ uint8_t *buf, int oob_required, int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ u8 *data_buf, *oob_buf = NULL; ++ bool erased_page; ++ int r; ++ ++ data_buf = buf; ++ oob_buf = oob_required ? chip->oob_poi : NULL; ++ ++ r = read_page_low(this, data_buf, oob_buf); ++ if (r) { ++ dev_err(this->dev, "failure to read page\n"); ++ return r; ++ } ++ ++ erased_page = empty_page_fixup(this, data_buf); ++ ++ return parse_read_errors(this, erased_page); ++} ++ ++/* implements ecc->read_oob() */ ++static int qcom_nandc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int r; ++ ++ clear_read_regs(this); ++ ++ this->use_ecc = true; ++ set_address(this, 0, page); ++ update_rw_regs(this, ecc->steps, true); ++ ++ r = read_page_low(this, NULL, chip->oob_poi); ++ if (r) ++ dev_err(this->dev, "failure to read oob\n"); ++ ++ return r; ++} ++ ++/* implements ecc->read_oob_raw(), used to read the bad block marker flag */ ++static int qcom_nandc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int start, length; ++ int r; ++ ++ /* ++ * configure registers for a raw page read, the address is set to the ++ * beginning of the last codeword, we don't care about reading ecc ++ * portion of oob, just the free stuff ++ */ ++ r = copy_last_cw(this, false, page); ++ if (r) ++ return r; ++ ++ /* ++ * reading raw oob has 2 parts, first the bad block byte, then the ++ * actual free oob region. perform a memcpy in two steps ++ */ ++ start = mtd->writesize - (this->cw_size * (ecc->steps - 1)); ++ length = chip->options & NAND_BUSWIDTH_16 ? 2 : 1; ++ ++ memcpy(oob, this->data_buffer + start, length); ++ ++ oob += length; ++ ++ start = this->cw_data - (ecc->steps << 2) + 1; ++ length = ecc->steps << 2; ++ ++ memcpy(oob, this->data_buffer + start, length); ++ ++ return 0; ++} ++ ++/* implements ecc->write_page() */ ++static int qcom_nandc_write_page(struct mtd_info *mtd, struct nand_chip *chip, ++ const uint8_t *buf, int oob_required) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ u8 *data_buf, *oob_buf; ++ int i, r = 0; ++ ++ clear_read_regs(this); ++ ++ data_buf = (u8 *) buf; ++ oob_buf = chip->oob_poi; ++ ++ this->use_ecc = true; ++ update_rw_regs(this, ecc->steps, false); ++ ++ for (i = 0; i < ecc->steps; i++) { ++ int data_size, oob_size; ++ ++ if (i == (ecc->steps - 1)) { ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ } else { ++ data_size = this->cw_data; ++ oob_size = ecc->bytes; ++ } ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, data_buf, data_size); ++ ++ /* ++ * we don't really need to write anything to oob for the ++ * first n - 1 codewords since these oob regions just ++ * contain ecc that's written by the controller itself ++ */ ++ if (i == (ecc->steps - 1)) ++ write_data_dma(this, FLASH_BUF_ACC + data_size, ++ oob_buf, oob_size); ++ config_cw_write_post(this); ++ ++ data_buf += data_size; ++ oob_buf += oob_size; ++ } ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failure to write page\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* ++ * implements ecc->write_oob() ++ * ++ * the NAND controller cannot write only data or only oob within a codeword, ++ * since ecc is calculated for the combined codeword. we first copy the ++ * entire contents for the last codeword(data + oob), replace the old oob ++ * with the new one in chip->oob_poi, and then write the entire codeword. ++ * this read-copy-write operation results in a slight perormance loss. ++ */ ++static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int free_boff; ++ int data_size, oob_size; ++ int r, status = 0; ++ ++ r = copy_last_cw(this, true, page); ++ if (r) ++ return r; ++ ++ clear_read_regs(this); ++ ++ /* calculate the data and oob size for the last codeword/step */ ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ ++ /* ++ * the location of spare data in the oob buffer, we could also use ++ * ecc->layout.oobfree here ++ */ ++ free_boff = ecc->bytes * (ecc->steps - 1); ++ ++ /* override new oob content to last codeword */ ++ memcpy(this->data_buffer + data_size, oob + free_boff, oob_size); ++ ++ this->use_ecc = true; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, false); ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, this->data_buffer, ++ data_size + oob_size); ++ config_cw_write_post(this); ++ ++ r = submit_descs(this); ++ ++ free_descs(this); ++ ++ if (r) { ++ dev_err(this->dev, "failure to write oob\n"); ++ return -EIO; ++ } ++ ++ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ++ ++ status = chip->waitfunc(mtd, chip); ++ ++ return status & NAND_STATUS_FAIL ? -EIO : 0; ++} ++ ++/* implements ecc->write_oob_raw(), used to write bad block marker flag */ ++static int qcom_nandc_write_oob_raw(struct mtd_info *mtd, ++ struct nand_chip *chip, int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int start, length; ++ int r, status = 0; ++ ++ r = copy_last_cw(this, false, page); ++ if (r) ++ return r; ++ ++ clear_read_regs(this); ++ ++ /* ++ * writing raw oob has 2 parts, first the bad block region, then the ++ * actual free region ++ */ ++ start = mtd->writesize - (this->cw_size * (ecc->steps - 1)); ++ length = chip->options & NAND_BUSWIDTH_16 ? 2 : 1; ++ ++ memcpy(this->data_buffer + start, oob, length); ++ ++ oob += length; ++ ++ start = this->cw_data - (ecc->steps << 2) + 1; ++ length = ecc->steps << 2; ++ ++ memcpy(this->data_buffer + start, oob, length); ++ ++ /* prepare write */ ++ this->use_ecc = false; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, false); ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, this->data_buffer, this->cw_size); ++ config_cw_write_post(this); ++ ++ r = submit_descs(this); ++ ++ free_descs(this); ++ ++ if (r) { ++ dev_err(this->dev, "failure to write updated oob\n"); ++ return -EIO; ++ } ++ ++ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ++ ++ status = chip->waitfunc(mtd, chip); ++ ++ return status & NAND_STATUS_FAIL ? -EIO : 0; ++} ++ ++/* ++ * the three functions below implement chip->read_byte(), chip->read_buf() ++ * and chip->write_buf() respectively. these aren't used for ++ * reading/writing page data, they are used for smaller data like reading ++ * id, status etc ++ */ ++static uint8_t qcom_nandc_read_byte(struct mtd_info *mtd) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ uint8_t *buf = this->data_buffer; ++ uint8_t ret = 0x0; ++ ++ if (this->last_command == NAND_CMD_STATUS) { ++ ret = this->status; ++ ++ this->status = NAND_STATUS_READY | NAND_STATUS_WP; ++ ++ return ret; ++ } ++ ++ if (this->buf_start < this->buf_count) ++ ret = buf[this->buf_start++]; ++ ++ return ret; ++} ++ ++static void qcom_nandc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ int real_len = min_t(size_t, len, this->buf_count - this->buf_start); ++ ++ memcpy(buf, this->data_buffer + this->buf_start, real_len); ++ this->buf_start += real_len; ++} ++ ++static void qcom_nandc_write_buf(struct mtd_info *mtd, const uint8_t *buf, ++ int len) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ int real_len = min_t(size_t, len, this->buf_count - this->buf_start); ++ ++ memcpy(this->data_buffer + this->buf_start, buf, real_len); ++ ++ this->buf_start += real_len; ++} ++ ++/* we support only one external chip for now */ ++static void qcom_nandc_select_chip(struct mtd_info *mtd, int chipnr) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ ++ if (chipnr <= 0) ++ return; ++ ++ dev_warn(this->dev, "invalid chip select\n"); ++} ++ ++/* ++ * NAND controller page layout info ++ * ++ * |-----------------------| |---------------------------------| ++ * | xx.......xx| | *********xx.......xx| ++ * | DATA xx..ECC..xx| | DATA **SPARE**xx..ECC..xx| ++ * | (516) xx.......xx| | (516-n*4) **(n*4)**xx.......xx| ++ * | xx.......xx| | *********xx.......xx| ++ * |-----------------------| |---------------------------------| ++ * codeword 1,2..n-1 codeword n ++ * <---(528/532 Bytes)----> <-------(528/532 Bytes)----------> ++ * ++ * n = number of codewords in the page ++ * . = ECC bytes ++ * * = spare bytes ++ * x = unused/reserved bytes ++ * ++ * 2K page: n = 4, spare = 16 bytes ++ * 4K page: n = 8, spare = 32 bytes ++ * 8K page: n = 16, spare = 64 bytes ++ * ++ * the qcom nand controller operates at a sub page/codeword level. each ++ * codeword is 528 and 532 bytes for 4 bit and 8 bit ECC modes respectively. ++ * the number of ECC bytes vary based on the ECC strength and the bus width. ++ * ++ * the first n - 1 codewords contains 516 bytes of user data, the remaining ++ * 12/16 bytes consist of ECC and reserved data. The nth codeword contains ++ * both user data and spare(oobavail) bytes that sum up to 516 bytes. ++ * ++ * the layout described above is used by the controller when the ECC block is ++ * enabled. When we read a page with ECC enabled, the unused/reserved bytes are ++ * skipped and not copied to our internal buffer. therefore, the nand_ecclayout ++ * layouts defined below doesn't consider the positions occupied by the reserved ++ * bytes ++ * ++ * when the ECC block is disabled, one unused byte (or two for 16 bit bus width) ++ * in the last codeword is the position of bad block marker. the bad block ++ * marker cannot be accessed when ECC is enabled. ++ * ++ */ ++ ++/* ++ * Layouts for different page sizes and ecc modes. We skip the eccpos field ++ * since it isn't needed for this driver ++ */ ++ ++/* 2K page, 4 bit ECC */ ++static struct nand_ecclayout layout_oob_64 = { ++ .eccbytes = 40, ++ .oobfree = { ++ { 30, 16 }, ++ }, ++}; ++ ++/* 4K page, 4 bit ECC, 8/16 bit bus width */ ++static struct nand_ecclayout layout_oob_128 = { ++ .eccbytes = 80, ++ .oobfree = { ++ { 70, 32 }, ++ }, ++}; ++ ++/* 4K page, 8 bit ECC, 8 bit bus width */ ++static struct nand_ecclayout layout_oob_224_x8 = { ++ .eccbytes = 104, ++ .oobfree = { ++ { 91, 32 }, ++ }, ++}; ++ ++/* 4K page, 8 bit ECC, 16 bit bus width */ ++static struct nand_ecclayout layout_oob_224_x16 = { ++ .eccbytes = 112, ++ .oobfree = { ++ { 98, 32 }, ++ }, ++}; ++ ++/* 8K page, 4 bit ECC, 8/16 bit bus width */ ++static struct nand_ecclayout layout_oob_256 = { ++ .eccbytes = 160, ++ .oobfree = { ++ { 151, 64 }, ++ }, ++}; ++ ++/* ++ * this is called before scan_ident, we do some minimal configurations so ++ * that reading ID and ONFI params work ++ */ ++static void qcom_nandc_pre_init(struct qcom_nandc_data *this) ++{ ++ /* kill onenand */ ++ nandc_write(this, SFLASHC_BURST_CFG, 0); ++ ++ /* enable ADM DMA */ ++ nandc_write(this, NAND_FLASH_CHIP_SELECT, DM_EN); ++ ++ /* save the original values of these registers */ ++ this->cmd1 = nandc_read(this, NAND_DEV_CMD1); ++ this->vld = nandc_read(this, NAND_DEV_CMD_VLD); ++ ++ /* initial status value */ ++ this->status = NAND_STATUS_READY | NAND_STATUS_WP; ++} ++ ++static int qcom_nandc_ecc_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage; ++ bool wide_bus; ++ ++ /* the nand controller fetches codewords/chunks of 512 bytes */ ++ cwperpage = mtd->writesize >> 9; ++ ++ ecc->strength = this->ecc_strength; ++ ++ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; ++ ++ if (ecc->strength >= 8) { ++ /* 8 bit ECC defaults to BCH ECC on all platforms */ ++ ecc->bytes = wide_bus ? 14 : 13; ++ } else { ++ /* ++ * if the controller supports BCH for 4 bit ECC, the controller ++ * uses lesser bytes for ECC. If RS is used, the ECC bytes is ++ * always 10 bytes ++ */ ++ if (this->ecc_modes & ECC_BCH_4BIT) ++ ecc->bytes = wide_bus ? 8 : 7; ++ else ++ ecc->bytes = 10; ++ } ++ ++ /* each step consists of 512 bytes of data */ ++ ecc->size = NANDC_STEP_SIZE; ++ ++ ecc->read_page = qcom_nandc_read_page; ++ ecc->read_oob = qcom_nandc_read_oob; ++ ecc->write_page = qcom_nandc_write_page; ++ ecc->write_oob = qcom_nandc_write_oob; ++ ++ /* ++ * the bad block marker is readable only when we read the page with ECC ++ * disabled. all the ops above run with ECC enabled. We need raw read ++ * and write function for oob in order to access bad block marker. ++ */ ++ ecc->read_oob_raw = qcom_nandc_read_oob_raw; ++ ecc->write_oob_raw = qcom_nandc_write_oob_raw; ++ ++ switch (mtd->oobsize) { ++ case 64: ++ ecc->layout = &layout_oob_64; ++ break; ++ case 128: ++ ecc->layout = &layout_oob_128; ++ break; ++ case 224: ++ if (wide_bus) ++ ecc->layout = &layout_oob_224_x16; ++ else ++ ecc->layout = &layout_oob_224_x8; ++ break; ++ case 256: ++ ecc->layout = &layout_oob_256; ++ break; ++ default: ++ dev_err(this->dev, "unsupported NAND device, oobsize %d\n", ++ mtd->oobsize); ++ return -ENODEV; ++ } ++ ++ ecc->mode = NAND_ECC_HW; ++ ++ /* enable ecc by default */ ++ this->use_ecc = true; ++ ++ return 0; ++} ++ ++static void qcom_nandc_hw_post_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = mtd->writesize / ecc->size; ++ int spare_bytes, bad_block_byte; ++ bool wide_bus; ++ int ecc_mode = 0; ++ ++ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; ++ ++ if (ecc->strength >= 8) { ++ this->cw_size = 532; ++ ++ spare_bytes = wide_bus ? 0 : 2; ++ ++ this->bch_enabled = true; ++ ecc_mode = 1; ++ } else { ++ this->cw_size = 528; ++ ++ if (this->ecc_modes & ECC_BCH_4BIT) { ++ spare_bytes = wide_bus ? 2 : 4; ++ ++ this->bch_enabled = true; ++ ecc_mode = 0; ++ } else { ++ spare_bytes = wide_bus ? 0 : 1; ++ } ++ } ++ ++ /* ++ * DATA_UD_BYTES varies based on whether the read/write command protects ++ * spare data with ECC too. We protect spare data by default, so we set ++ * it to main + spare data, which are 512 and 4 bytes respectively. ++ */ ++ this->cw_data = 516; ++ ++ bad_block_byte = mtd->writesize - this->cw_size * (cwperpage - 1) + 1; ++ ++ this->cfg0 = (cwperpage - 1) << CW_PER_PAGE ++ | this->cw_data << UD_SIZE_BYTES ++ | 0 << DISABLE_STATUS_AFTER_WRITE ++ | 5 << NUM_ADDR_CYCLES ++ | ecc->bytes << ECC_PARITY_SIZE_BYTES_RS ++ | 0 << STATUS_BFR_READ ++ | 1 << SET_RD_MODE_AFTER_STATUS ++ | spare_bytes << SPARE_SIZE_BYTES; ++ ++ this->cfg1 = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | bad_block_byte << BAD_BLOCK_BYTE_NUM ++ | 0 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | wide_bus << WIDE_FLASH ++ | this->bch_enabled << ENABLE_BCH_ECC; ++ ++ this->cfg0_raw = (cwperpage - 1) << CW_PER_PAGE ++ | this->cw_size << UD_SIZE_BYTES ++ | 5 << NUM_ADDR_CYCLES ++ | 0 << SPARE_SIZE_BYTES; ++ ++ this->cfg1_raw = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | 17 << BAD_BLOCK_BYTE_NUM ++ | 1 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | wide_bus << WIDE_FLASH ++ | 1 << DEV0_CFG1_ECC_DISABLE; ++ ++ this->ecc_bch_cfg = this->bch_enabled << ECC_CFG_ECC_DISABLE ++ | 0 << ECC_SW_RESET ++ | this->cw_data << ECC_NUM_DATA_BYTES ++ | 1 << ECC_FORCE_CLK_OPEN ++ | ecc_mode << ECC_MODE ++ | ecc->bytes << ECC_PARITY_SIZE_BYTES_BCH; ++ ++ this->ecc_buf_cfg = 0x203 << NUM_STEPS; ++ ++ this->clrflashstatus = FS_READY_BSY_N; ++ this->clrreadstatus = 0xc0; ++ ++ dev_dbg(this->dev, ++ "cfg0 %x cfg1 %x ecc_buf_cfg %x ecc_bch cfg %x cw_size %d cw_data %d strength %d parity_bytes %d steps %d\n", ++ this->cfg0, this->cfg1, this->ecc_buf_cfg, ++ this->ecc_bch_cfg, this->cw_size, this->cw_data, ++ ecc->strength, ecc->bytes, cwperpage); ++} ++ ++static int qcom_nandc_alloc(struct qcom_nandc_data *this) ++{ ++ int r; ++ ++ r = dma_set_coherent_mask(this->dev, DMA_BIT_MASK(32)); ++ if (r) { ++ dev_err(this->dev, "failed to set DMA mask\n"); ++ return r; ++ } ++ ++ /* ++ * we use the internal buffer for reading ONFI params, reading small ++ * data like ID and status, and preforming read-copy-write operations ++ * when writing to a codeword partially. 532 is the maximum possible ++ * size of a codeword for our nand controller ++ */ ++ this->buf_size = 532; ++ ++ this->data_buffer = devm_kzalloc(this->dev, this->buf_size, GFP_KERNEL); ++ if (!this->data_buffer) ++ return -ENOMEM; ++ ++ this->regs = devm_kzalloc(this->dev, sizeof(*this->regs), GFP_KERNEL); ++ if (!this->regs) ++ return -ENOMEM; ++ ++ this->reg_read_buf = devm_kzalloc(this->dev, ++ MAX_REG_RD * sizeof(*this->reg_read_buf), ++ GFP_KERNEL); ++ if (!this->reg_read_buf) ++ return -ENOMEM; ++ ++ INIT_LIST_HEAD(&this->list); ++ ++ this->chan = dma_request_slave_channel(this->dev, "rxtx"); ++ if (!this->chan) { ++ dev_err(this->dev, "failed to request slave channel\n"); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static void qcom_nandc_unalloc(struct qcom_nandc_data *this) ++{ ++ dma_release_channel(this->chan); ++} ++ ++static int qcom_nandc_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct device_node *np = this->dev->of_node; ++ struct mtd_part_parser_data ppdata = { .of_node = np }; ++ int r; ++ ++ mtd->priv = chip; ++ mtd->name = "qcom-nandc"; ++ mtd->owner = THIS_MODULE; ++ ++ chip->priv = this; ++ ++ chip->cmdfunc = qcom_nandc_command; ++ chip->select_chip = qcom_nandc_select_chip; ++ chip->read_byte = qcom_nandc_read_byte; ++ chip->read_buf = qcom_nandc_read_buf; ++ chip->write_buf = qcom_nandc_write_buf; ++ ++ chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER; ++ if (this->bus_width == 16) ++ chip->options |= NAND_BUSWIDTH_16; ++ ++ chip->bbt_options = NAND_BBT_ACCESS_BBM_RAW; ++ if (of_get_nand_on_flash_bbt(np)) ++ chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; ++ ++ qcom_nandc_pre_init(this); ++ ++ r = nand_scan_ident(mtd, 1, NULL); ++ if (r) ++ return r; ++ ++ r = qcom_nandc_ecc_init(this); ++ if (r) ++ return r; ++ ++ qcom_nandc_hw_post_init(this); ++ ++ r = nand_scan_tail(mtd); ++ if (r) ++ return r; ++ ++ return mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); ++} ++ ++static int qcom_nandc_parse_dt(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this = platform_get_drvdata(pdev); ++ struct device_node *np = this->dev->of_node; ++ int r; ++ ++ this->ecc_strength = of_get_nand_ecc_strength(np); ++ if (this->ecc_strength < 0) { ++ dev_warn(this->dev, ++ "incorrect ecc strength, setting to 4 bits/step\n"); ++ this->ecc_strength = 4; ++ } ++ ++ this->bus_width = of_get_nand_bus_width(np); ++ if (this->bus_width < 0) { ++ dev_warn(this->dev, "incorrect bus width, setting to 8\n"); ++ this->bus_width = 8; ++ } ++ ++ r = of_property_read_u32(np, "qcom,cmd-crci", &this->cmd_crci); ++ if (r) { ++ dev_err(this->dev, "command CRCI unspecified\n"); ++ return r; ++ } ++ ++ r = of_property_read_u32(np, "qcom,data-crci", &this->data_crci); ++ if (r) { ++ dev_err(this->dev, "data CRCI unspecified\n"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++static int qcom_nandc_probe(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this; ++ const struct of_device_id *match; ++ int r; ++ ++ this = devm_kzalloc(&pdev->dev, sizeof(*this), GFP_KERNEL); ++ if (!this) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, this); ++ ++ this->pdev = pdev; ++ this->dev = &pdev->dev; ++ ++ match = of_match_device(pdev->dev.driver->of_match_table, &pdev->dev); ++ if (!match) { ++ dev_err(&pdev->dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ if (!match->data) { ++ dev_err(&pdev->dev, "failed to get device data\n"); ++ return -ENODEV; ++ } ++ ++ this->ecc_modes = (u32) match->data; ++ ++ this->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ this->base = devm_ioremap_resource(&pdev->dev, this->res); ++ if (IS_ERR(this->base)) ++ return PTR_ERR(this->base); ++ ++ this->core_clk = devm_clk_get(&pdev->dev, "core"); ++ if (IS_ERR(this->core_clk)) ++ return PTR_ERR(this->core_clk); ++ ++ this->aon_clk = devm_clk_get(&pdev->dev, "aon"); ++ if (IS_ERR(this->aon_clk)) ++ return PTR_ERR(this->aon_clk); ++ ++ r = qcom_nandc_parse_dt(pdev); ++ if (r) ++ return r; ++ ++ r = qcom_nandc_alloc(this); ++ if (r) ++ return r; ++ ++ r = clk_prepare_enable(this->core_clk); ++ if (r) ++ goto err_core_clk; ++ ++ r = clk_prepare_enable(this->aon_clk); ++ if (r) ++ goto err_aon_clk; ++ ++ r = qcom_nandc_init(this); ++ if (r) ++ goto err_init; ++ ++ return 0; ++ ++err_init: ++ clk_disable_unprepare(this->aon_clk); ++err_aon_clk: ++ clk_disable_unprepare(this->core_clk); ++err_core_clk: ++ qcom_nandc_unalloc(this); ++ ++ return r; ++} ++ ++static int qcom_nandc_remove(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this = platform_get_drvdata(pdev); ++ ++ qcom_nandc_unalloc(this); ++ ++ clk_disable_unprepare(this->aon_clk); ++ clk_disable_unprepare(this->core_clk); ++ ++ return 0; ++} ++ ++#define EBI2_NANDC_ECC_MODES (ECC_RS_4BIT | ECC_BCH_8BIT) ++ ++/* ++ * data will hold a struct pointer containing more differences once we support ++ * more IPs ++ */ ++static const struct of_device_id qcom_nandc_of_match[] = { ++ { .compatible = "qcom,ebi2-nandc", ++ .data = (void *) EBI2_NANDC_ECC_MODES, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, qcom_nandc_of_match); ++ ++static struct platform_driver qcom_nandc_driver = { ++ .driver = { ++ .name = "qcom-nandc", ++ .of_match_table = qcom_nandc_of_match, ++ }, ++ .probe = qcom_nandc_probe, ++ .remove = qcom_nandc_remove, ++}; ++module_platform_driver(qcom_nandc_driver); ++ ++MODULE_AUTHOR("Archit Taneja "); ++MODULE_DESCRIPTION("Qualcomm NAND Controller driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/mtd/nand/Makefile ++++ b/drivers/mtd/nand/Makefile +@@ -50,5 +50,6 @@ obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740 + obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/ + obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o + obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/ ++obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o + + nand-objs := nand_base.o nand_bbt.o nand_timings.o diff --git a/target/linux/ipq806x/patches-3.18/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch b/target/linux/ipq806x/patches-3.18/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch new file mode 100644 index 0000000..6530eb1 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,3/5] dt/bindings: qcom_nandc: Add DT bindings +From: Archit Taneja +X-Patchwork-Id: 6927141 +Message-Id: <1438578498-32254-4-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja , devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:16 +0530 + +Add DT bindings document for the Qualcomm NAND controller driver. + +Cc: devicetree@vger.kernel.org + +v3: +- Don't use '0x' when specifying nand controller address space +- Add optional property for on-flash bbt usage + +Acked-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +.../devicetree/bindings/mtd/qcom_nandc.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/mtd/qcom_nandc.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/mtd/qcom_nandc.txt +@@ -0,0 +1,49 @@ ++* Qualcomm NAND controller ++ ++Required properties: ++- compatible: should be "qcom,ebi2-nand" for IPQ806x ++- reg: MMIO address range ++- clocks: must contain core clock and always on clock ++- clock-names: must contain "core" for the core clock and "aon" for the ++ always on clock ++- dmas: DMA specifier, consisting of a phandle to the ADM DMA ++ controller node and the channel number to be used for ++ NAND. Refer to dma.txt and qcom_adm.txt for more details ++- dma-names: must be "rxtx" ++- qcom,cmd-crci: must contain the ADM command type CRCI block instance ++ number specified for the NAND controller on the given ++ platform ++- qcom,data-crci: must contain the ADM data type CRCI block instance ++ number specified for the NAND controller on the given ++ platform ++ ++Optional properties: ++- nand-bus-width: bus width. Must be 8 or 16. If not present, 8 is chosen ++ as default ++ ++- nand-ecc-strength: number of bits to correct per ECC step. Must be 4 or 8 ++ bits. If not present, 4 is chosen as default ++- nand-on-flash-bbt: Create/use on-flash bad block table ++ ++The device tree may optionally contain sub-nodes describing partitions of the ++address space. See partition.txt for more detail. ++ ++Example: ++ ++nand@1ac00000 { ++ compatible = "qcom,ebi2-nandc"; ++ reg = <0x1ac00000 0x800>; ++ ++ clocks = <&gcc EBI2_CLK>, ++ <&gcc EBI2_AON_CLK>; ++ clock-names = "core", "aon"; ++ ++ dmas = <&adm_dma 3>; ++ dma-names = "rxtx"; ++ qcom,cmd-crci = <15>; ++ qcom,data-crci = <3>; ++ ++ partition@0 { ++ ... ++ }; ++}; diff --git a/target/linux/ipq806x/patches-3.18/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch b/target/linux/ipq806x/patches-3.18/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch new file mode 100644 index 0000000..29e1592 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch @@ -0,0 +1,51 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,4/5] arm: qcom: dts: Add NAND controller node for ipq806x +From: Archit Taneja +X-Patchwork-Id: 6927121 +Message-Id: <1438578498-32254-5-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja , devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:17 +0530 + +The nand controller in IPQ806x is of the 'EBI2 type'. Use the corresponding +compatible string. + +Cc: devicetree@vger.kernel.org + +Reviewed-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +arch/arm/boot/dts/qcom-ipq8064.dtsi | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -682,6 +682,22 @@ + status = "disabled"; + }; + ++ nand@1ac00000 { ++ compatible = "qcom,ebi2-nandc"; ++ reg = <0x1ac00000 0x800>; ++ ++ clocks = <&gcc EBI2_CLK>, ++ <&gcc EBI2_AON_CLK>; ++ clock-names = "core", "aon"; ++ ++ dmas = <&adm_dma 3>; ++ dma-names = "rxtx"; ++ qcom,cmd-crci = <15>; ++ qcom,data-crci = <3>; ++ ++ status = "disabled"; ++ }; ++ + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-3.18/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch b/target/linux/ipq806x/patches-3.18/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch new file mode 100644 index 0000000..d62564c --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch @@ -0,0 +1,79 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,5/5] arm: qcom: dts: Enable NAND node on IPQ8064 AP148 platform +From: Archit Taneja +X-Patchwork-Id: 6927091 +Message-Id: <1438578498-32254-6-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja , devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:18 +0530 + +Enable the NAND controller node on the AP148 platform. Provide pinmux +information. + +Cc: devicetree@vger.kernel.org + +Signed-off-by: Archit Taneja + +--- +arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 36 ++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -61,6 +61,31 @@ + bias-none; + }; + }; ++ ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -147,5 +172,19 @@ + pinctrl-0 = <&pcie1_pins>; + pinctrl-names = "default"; + }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ }; + }; + }; ++ ++&adm_dma { ++ status = "ok"; ++}; diff --git a/target/linux/ipq806x/patches-3.18/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch b/target/linux/ipq806x/patches-3.18/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch new file mode 100644 index 0000000..cae7f15 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch @@ -0,0 +1,11 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -181,6 +181,8 @@ + + nand-ecc-strength = <4>; + nand-bus-width = <8>; ++ ++ linux,part-probe = "qcom-smem"; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-3.18/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch b/target/linux/ipq806x/patches-3.18/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch new file mode 100644 index 0000000..cb740de --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch @@ -0,0 +1,62 @@ +From b12e230f09d4481424e6a5d7e2ae566b6954e83f Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Wed, 29 Apr 2015 15:21:46 -0700 +Subject: [PATCH] HACK: arch: arm: force ZRELADDR on arch-qcom + +ARCH_QCOM is using the ARCH_MULTIPLATFORM option, as now recommended +on most ARM architectures. This automatically calculate ZRELADDR by +masking PHYS_OFFSET with 0xf8000000. + +However, on IPQ806x, the first ~20MB of RAM is reserved for the hardware +network accelerators, and the bootloader removes this section from the +layout passed from the ATAGS (when used). + +For newer bootloader, when DT is used, this is not a problem, we just +reserve this memory in the device tree. But if the bootloader doesn't +have DT support, then ATAGS have to be used. In this case, the ARM +decompressor will position the kernel in this low mem, which will not be +in the RAM section mapped by the bootloader, which means the kernel will +freeze in the middle of the boot process trying to map the memory. + +As a work around, this patch allows disabling AUTO_ZRELADDR when +ARCH_QCOM is selected. It makes the zImage usage possible on bootloaders +which don't support device-tree, which is the case on certain early +IPQ806x based designs. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/Kconfig | 2 +- + arch/arm/Makefile | 2 ++ + arch/arm/mach-qcom/Makefile.boot | 1 + + 3 files changed, 4 insertions(+), 1 deletion(-) + create mode 100644 arch/arm/mach-qcom/Makefile.boot + +--- a/arch/arm/Kconfig ++++ b/arch/arm/Kconfig +@@ -311,7 +311,7 @@ config ARCH_MULTIPLATFORM + select ARCH_WANT_OPTIONAL_GPIOLIB + select ARM_HAS_SG_CHAIN + select ARM_PATCH_PHYS_VIRT +- select AUTO_ZRELADDR ++ select AUTO_ZRELADDR if !ARCH_QCOM + select CLKSRC_OF + select COMMON_CLK + select GENERIC_CLOCKEVENTS +--- a/arch/arm/Makefile ++++ b/arch/arm/Makefile +@@ -240,9 +240,11 @@ MACHINE := arch/arm/mach-$(word 1,$(mac + else + MACHINE := + endif ++ifeq ($(CONFIG_ARCH_QCOM),) + ifeq ($(CONFIG_ARCH_MULTIPLATFORM),y) + MACHINE := + endif ++endif + + machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y)) + platdirs := $(patsubst %,arch/arm/plat-%/,$(sort $(plat-y))) +--- /dev/null ++++ b/arch/arm/mach-qcom/Makefile.boot +@@ -0,0 +1 @@ ++zreladdr-y+= 0x42208000 diff --git a/target/linux/ipq806x/patches-3.18/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch b/target/linux/ipq806x/patches-3.18/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch new file mode 100644 index 0000000..a9b83a1 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch @@ -0,0 +1,387 @@ +From 7e77aa188a7a7c4391856a9e5ef5ef58f769e679 Mon Sep 17 00:00:00 2001 +From: Jonas Gorski +Date: Sun, 9 Aug 2015 13:02:38 +0200 +Subject: [PATCH] ARM: qcom: add Netgear Nighthawk X4 R7500 device tree + +Signed-off-by: Jonas Gorski +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/qcom-ipq8064-r7500.dts | 370 +++++++++++++++++++++++++++++++ + 2 files changed, 371 insertions(+) + create mode 100644 arch/arm/boot/dts/qcom-ipq8064-r7500.dts + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -361,6 +361,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \ + qcom-apq8084-mtp.dtb \ + qcom-ipq8064-ap148.dtb \ + qcom-ipq8064-db149.dtb \ ++ qcom-ipq8064-r7500.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-r7500.dts +@@ -0,0 +1,362 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++#include ++ ++/ { ++ model = "Netgear Nighthawk X4 R7500"; ++ compatible = "netgear,r7500", "qcom,ipq8064"; ++ ++ memory@0 { ++ reg = <0x42000000 0xe000000>; ++ device_type = "memory"; ++ }; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ aliases { ++ serial0 = &uart4; ++ mdio-gpio0 = &mdio0; ++ }; ++ ++ chosen { ++ bootargs = "rootfstype=squashfs noinitrd"; ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ }; ++ ++ gsbi@16300000 { ++ qcom,mode = ; ++ status = "ok"; ++ serial@16340000 { ++ status = "ok"; ++ }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ qcadata@0 { ++ label = "qcadata"; ++ reg = <0x0000000 0x0c80000>; ++ read-only; ++ }; ++ ++ APPSBL@c80000 { ++ label = "APPSBL"; ++ reg = <0x0c80000 0x0500000>; ++ read-only; ++ }; ++ ++ APPSBLENV@1180000 { ++ label = "APPSBLENV"; ++ reg = <0x1180000 0x0080000>; ++ read-only; ++ }; ++ ++ art: art@1200000 { ++ label = "art"; ++ reg = <0x1200000 0x0140000>; ++ read-only; ++ }; ++ ++ kernel@1340000 { ++ label = "kernel"; ++ reg = <0x1340000 0x0200000>; ++ }; ++ ++ ubi@1540000 { ++ label = "ubi"; ++ reg = <0x1540000 0x1800000>; ++ }; ++ ++ netgear@2d40000 { ++ label = "netgear"; ++ reg = <0x2d40000 0x0c00000>; ++ read-only; ++ }; ++ ++ reserve@3940000 { ++ label = "reserve"; ++ reg = <0x3940000 0x46c0000>; ++ read-only; ++ }; ++ ++ firmware@1340000 { ++ label = "firmware"; ++ reg = <0x1340000 0x1a00000>; ++ }; ++ ++ }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ phy-handle = <&phy4>; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ ++ mtd-mac-address = <&art 6>; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ mtd-mac-address = <&art 0>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ ++ wifi { ++ label = "wifi"; ++ gpios = <&qcom_pinmux 6 1>; ++ linux,code = ; ++ }; ++ ++ reset { ++ label = "reset"; ++ gpios = <&qcom_pinmux 54 1>; ++ linux,code = ; ++ }; ++ ++ wps { ++ label = "wps"; ++ gpios = <&qcom_pinmux 65 1>; ++ linux,code = ; ++ }; ++ }; ++ ++ gpio-leds { ++ compatible = "gpio-leds"; ++ ++ usb1 { ++ label = "r7500:amber:usb1"; ++ gpios = <&qcom_pinmux 7 0>; ++ }; ++ ++ usb3 { ++ label = "r7500:amber:usb3"; ++ gpios = <&qcom_pinmux 8 0>; ++ }; ++ ++ status { ++ label = "r7500:amber:status"; ++ gpios = <&qcom_pinmux 9 0>; ++ }; ++ ++ internet { ++ label = "r7500:white:internet"; ++ gpios = <&qcom_pinmux 22 0>; ++ }; ++ ++ wan { ++ label = "r7500:white:wan"; ++ gpios = <&qcom_pinmux 23 0>; ++ }; ++ ++ wps { ++ label = "r7500:white:wps"; ++ gpios = <&qcom_pinmux 24 0>; ++ }; ++ ++ esata { ++ label = "r7500:white:esata"; ++ gpios = <&qcom_pinmux 26 0>; ++ }; ++ ++ power { ++ label = "r7500:white:power"; ++ gpios = <&qcom_pinmux 53 0>; ++ default-state = "on"; ++ }; ++ ++ rfkill { ++ label = "r7500:white:rfkill"; ++ gpios = <&qcom_pinmux 64 0>; ++ }; ++ ++ wifi5g { ++ label = "r7500:white:wifi5g"; ++ gpios = <&qcom_pinmux 67 0>; ++ }; ++ }; ++}; ++ ++&adm_dma { ++ status = "ok"; ++}; diff --git a/target/linux/ipq806x/patches-3.18/302-mtd-qcom-smem-rename-rootfs-ubi.patch b/target/linux/ipq806x/patches-3.18/302-mtd-qcom-smem-rename-rootfs-ubi.patch new file mode 100644 index 0000000..471a87b --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/302-mtd-qcom-smem-rename-rootfs-ubi.patch @@ -0,0 +1,13 @@ +--- a/drivers/mtd/qcom_smem_part.c ++++ b/drivers/mtd/qcom_smem_part.c +@@ -192,6 +192,10 @@ static int parse_qcom_smem_partitions(st + m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz); + m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz); + ++ /* "rootfs" conflicts with OpenWrt auto mounting */ ++ if (mtd_type_is_nand(master) && !strcmp(m_part->name, "rootfs")) ++ m_part->name = "ubi"; ++ + /* + * The last SMEM partition may have its size marked as + * something like 0xffffffff, which means "until the end of the diff --git a/target/linux/ipq806x/patches-3.18/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch b/target/linux/ipq806x/patches-3.18/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch new file mode 100644 index 0000000..22d28ac --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch @@ -0,0 +1,733 @@ +From 2fbb18f85826a9ba308fedb2cf90d3a661a39fd7 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd +Date: Fri, 27 Mar 2015 00:16:14 -0700 +Subject: [PATCH] clk: qcom: Add support for NSS/GMAC clocks and resets + +Add the NSS/GMAC clocks and the TCM clock and NSS resets. + +Signed-off-by: Stephen Boyd +--- + drivers/clk/qcom/gcc-ipq806x.c | 594 ++++++++++++++++++++++++++- + drivers/clk/qcom/gcc-ipq806x.c.rej | 50 +++ + include/dt-bindings/clock/qcom,gcc-ipq806x.h | 2 + + include/dt-bindings/reset/qcom,gcc-ipq806x.h | 43 ++ + 4 files changed, 688 insertions(+), 1 deletion(-) + create mode 100644 drivers/clk/qcom/gcc-ipq806x.c.rej + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -209,11 +209,46 @@ static struct clk_regmap pll14_vote = { + }, + }; + ++#define NSS_PLL_RATE(f, _l, _m, _n, i) \ ++ { \ ++ .freq = f, \ ++ .l = _l, \ ++ .m = _m, \ ++ .n = _n, \ ++ .ibits = i, \ ++ } ++ ++static struct pll_freq_tbl pll18_freq_tbl[] = { ++ NSS_PLL_RATE(550000000, 44, 0, 1, 0x01495625), ++ NSS_PLL_RATE(733000000, 58, 16, 25, 0x014b5625), ++}; ++ ++static struct clk_pll pll18 = { ++ .l_reg = 0x31a4, ++ .m_reg = 0x31a8, ++ .n_reg = 0x31ac, ++ .config_reg = 0x31b4, ++ .mode_reg = 0x31a0, ++ .status_reg = 0x31b8, ++ .status_bit = 16, ++ .post_div_shift = 16, ++ .post_div_width = 1, ++ .freq_tbl = pll18_freq_tbl, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "pll18", ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .ops = &clk_pll_ops, ++ }, ++}; ++ + #define P_PXO 0 + #define P_PLL8 1 + #define P_PLL3 1 + #define P_PLL0 2 + #define P_CXO 2 ++#define P_PLL14 3 ++#define P_PLL18 4 + + static const u8 gcc_pxo_pll8_map[] = { + [P_PXO] = 0, +@@ -264,6 +299,22 @@ static const char *gcc_pxo_pll8_pll0_map + "pll0_vote", + }; + ++static const u8 gcc_pxo_pll8_pll14_pll18_pll0_map[] = { ++ [P_PXO] = 0 , ++ [P_PLL8] = 4, ++ [P_PLL0] = 2, ++ [P_PLL14] = 5, ++ [P_PLL18] = 1, ++}; ++ ++static const char *gcc_pxo_pll8_pll14_pll18_pll0[] = { ++ "pxo", ++ "pll8_vote", ++ "pll0_vote", ++ "pll14", ++ "pll18", ++}; ++ + static struct freq_tbl clk_tbl_gsbi_uart[] = { + { 1843200, P_PLL8, 2, 6, 625 }, + { 3686400, P_PLL8, 2, 12, 625 }, +@@ -2269,6 +2320,472 @@ static struct clk_branch ebi2_aon_clk = + }, + }; + ++static const struct freq_tbl clk_tbl_gmac[] = { ++ { 133000000, P_PLL0, 1, 50, 301 }, ++ { 266000000, P_PLL0, 1, 127, 382 }, ++ { } ++}; ++ ++static struct clk_dyn_rcg gmac_core1_src = { ++ .ns_reg[0] = 0x3cac, ++ .ns_reg[1] = 0x3cb0, ++ .md_reg[0] = 0x3ca4, ++ .md_reg[1] = 0x3ca8, ++ .bank_reg = 0x3ca0, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3ca0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core1_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core1_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 4, ++ .hwcg_reg = 0x3cb4, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3cb4, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core1_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core1_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg gmac_core2_src = { ++ .ns_reg[0] = 0x3ccc, ++ .ns_reg[1] = 0x3cd0, ++ .md_reg[0] = 0x3cc4, ++ .md_reg[1] = 0x3cc8, ++ .bank_reg = 0x3ca0, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3cc0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core2_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core2_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 5, ++ .hwcg_reg = 0x3cd4, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3cd4, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core2_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core2_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg gmac_core3_src = { ++ .ns_reg[0] = 0x3cec, ++ .ns_reg[1] = 0x3cf0, ++ .md_reg[0] = 0x3ce4, ++ .md_reg[1] = 0x3ce8, ++ .bank_reg = 0x3ce0, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3ce0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core3_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core3_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 6, ++ .hwcg_reg = 0x3cf4, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3cf4, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core3_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core3_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg gmac_core4_src = { ++ .ns_reg[0] = 0x3d0c, ++ .ns_reg[1] = 0x3d10, ++ .md_reg[0] = 0x3d04, ++ .md_reg[1] = 0x3d08, ++ .bank_reg = 0x3d00, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3d00, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core4_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core4_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 7, ++ .hwcg_reg = 0x3d14, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3d14, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core4_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core4_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static const struct freq_tbl clk_tbl_nss_tcm[] = { ++ { 266000000, P_PLL0, 3, 0, 0 }, ++ { 400000000, P_PLL0, 2, 0, 0 }, ++ { } ++}; ++ ++static struct clk_dyn_rcg nss_tcm_src = { ++ .ns_reg[0] = 0x3dc4, ++ .ns_reg[1] = 0x3dc8, ++ .bank_reg = 0x3dc0, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 4, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 4, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_nss_tcm, ++ .clkr = { ++ .enable_reg = 0x3dc0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "nss_tcm_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch nss_tcm_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 14, ++ .clkr = { ++ .enable_reg = 0x3dd0, ++ .enable_mask = BIT(6) | BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "nss_tcm_clk", ++ .parent_names = (const char *[]){ ++ "nss_tcm_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static const struct freq_tbl clk_tbl_nss[] = { ++ { 110000000, P_PLL18, 1, 1, 5 }, ++ { 275000000, P_PLL18, 2, 0, 0 }, ++ { 550000000, P_PLL18, 1, 0, 0 }, ++ { 733000000, P_PLL18, 1, 0, 0 }, ++ { } ++}; ++ ++static struct clk_dyn_rcg ubi32_core1_src_clk = { ++ .ns_reg[0] = 0x3d2c, ++ .ns_reg[1] = 0x3d30, ++ .md_reg[0] = 0x3d24, ++ .md_reg[1] = 0x3d28, ++ .bank_reg = 0x3d20, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_nss, ++ .clkr = { ++ .enable_reg = 0x3d20, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "ubi32_core1_src_clk", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg ubi32_core2_src_clk = { ++ .ns_reg[0] = 0x3d4c, ++ .ns_reg[1] = 0x3d50, ++ .md_reg[0] = 0x3d44, ++ .md_reg[1] = 0x3d48, ++ .bank_reg = 0x3d40, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_nss, ++ .clkr = { ++ .enable_reg = 0x3d40, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "ubi32_core2_src_clk", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, ++ }, ++ }, ++}; ++ + static struct clk_regmap *gcc_ipq806x_clks[] = { + [PLL0] = &pll0.clkr, + [PLL0_VOTE] = &pll0_vote, +@@ -2277,6 +2794,7 @@ static struct clk_regmap *gcc_ipq806x_cl + [PLL8_VOTE] = &pll8_vote, + [PLL14] = &pll14.clkr, + [PLL14_VOTE] = &pll14_vote, ++ [PLL18] = &pll18.clkr, + [GSBI1_UART_SRC] = &gsbi1_uart_src.clkr, + [GSBI1_UART_CLK] = &gsbi1_uart_clk.clkr, + [GSBI2_UART_SRC] = &gsbi2_uart_src.clkr, +@@ -2376,6 +2894,18 @@ static struct clk_regmap *gcc_ipq806x_cl + [PLL9] = &hfpll0.clkr, + [PLL10] = &hfpll1.clkr, + [PLL12] = &hfpll_l2.clkr, ++ [GMAC_CORE1_CLK_SRC] = &gmac_core1_src.clkr, ++ [GMAC_CORE1_CLK] = &gmac_core1_clk.clkr, ++ [GMAC_CORE2_CLK_SRC] = &gmac_core2_src.clkr, ++ [GMAC_CORE2_CLK] = &gmac_core2_clk.clkr, ++ [GMAC_CORE3_CLK_SRC] = &gmac_core3_src.clkr, ++ [GMAC_CORE3_CLK] = &gmac_core3_clk.clkr, ++ [GMAC_CORE4_CLK_SRC] = &gmac_core4_src.clkr, ++ [GMAC_CORE4_CLK] = &gmac_core4_clk.clkr, ++ [UBI32_CORE1_CLK_SRC] = &ubi32_core1_src_clk.clkr, ++ [UBI32_CORE2_CLK_SRC] = &ubi32_core2_src_clk.clkr, ++ [NSSTCM_CLK_SRC] = &nss_tcm_src.clkr, ++ [NSSTCM_CLK] = &nss_tcm_clk.clkr, + }; + + static const struct qcom_reset_map gcc_ipq806x_resets[] = { +@@ -2494,6 +3024,48 @@ static const struct qcom_reset_map gcc_i + [USB30_1_PHY_RESET] = { 0x3b58, 0 }, + [NSSFB0_RESET] = { 0x3b60, 6 }, + [NSSFB1_RESET] = { 0x3b60, 7 }, ++ [UBI32_CORE1_CLKRST_CLAMP_RESET] = { 0x3d3c, 3}, ++ [UBI32_CORE1_CLAMP_RESET] = { 0x3d3c, 2 }, ++ [UBI32_CORE1_AHB_RESET] = { 0x3d3c, 1 }, ++ [UBI32_CORE1_AXI_RESET] = { 0x3d3c, 0 }, ++ [UBI32_CORE2_CLKRST_CLAMP_RESET] = { 0x3d5c, 3 }, ++ [UBI32_CORE2_CLAMP_RESET] = { 0x3d5c, 2 }, ++ [UBI32_CORE2_AHB_RESET] = { 0x3d5c, 1 }, ++ [UBI32_CORE2_AXI_RESET] = { 0x3d5c, 0 }, ++ [GMAC_CORE1_RESET] = { 0x3cbc, 0 }, ++ [GMAC_CORE2_RESET] = { 0x3cdc, 0 }, ++ [GMAC_CORE3_RESET] = { 0x3cfc, 0 }, ++ [GMAC_CORE4_RESET] = { 0x3d1c, 0 }, ++ [GMAC_AHB_RESET] = { 0x3e24, 0 }, ++ [NSS_CH0_RST_RX_CLK_N_RESET] = { 0x3b60, 0 }, ++ [NSS_CH0_RST_TX_CLK_N_RESET] = { 0x3b60, 1 }, ++ [NSS_CH0_RST_RX_125M_N_RESET] = { 0x3b60, 2 }, ++ [NSS_CH0_HW_RST_RX_125M_N_RESET] = { 0x3b60, 3 }, ++ [NSS_CH0_RST_TX_125M_N_RESET] = { 0x3b60, 4 }, ++ [NSS_CH1_RST_RX_CLK_N_RESET] = { 0x3b60, 5 }, ++ [NSS_CH1_RST_TX_CLK_N_RESET] = { 0x3b60, 6 }, ++ [NSS_CH1_RST_RX_125M_N_RESET] = { 0x3b60, 7 }, ++ [NSS_CH1_HW_RST_RX_125M_N_RESET] = { 0x3b60, 8 }, ++ [NSS_CH1_RST_TX_125M_N_RESET] = { 0x3b60, 9 }, ++ [NSS_CH2_RST_RX_CLK_N_RESET] = { 0x3b60, 10 }, ++ [NSS_CH2_RST_TX_CLK_N_RESET] = { 0x3b60, 11 }, ++ [NSS_CH2_RST_RX_125M_N_RESET] = { 0x3b60, 12 }, ++ [NSS_CH2_HW_RST_RX_125M_N_RESET] = { 0x3b60, 13 }, ++ [NSS_CH2_RST_TX_125M_N_RESET] = { 0x3b60, 14 }, ++ [NSS_CH3_RST_RX_CLK_N_RESET] = { 0x3b60, 15 }, ++ [NSS_CH3_RST_TX_CLK_N_RESET] = { 0x3b60, 16 }, ++ [NSS_CH3_RST_RX_125M_N_RESET] = { 0x3b60, 17 }, ++ [NSS_CH3_HW_RST_RX_125M_N_RESET] = { 0x3b60, 18 }, ++ [NSS_CH3_RST_TX_125M_N_RESET] = { 0x3b60, 19 }, ++ [NSS_RST_RX_250M_125M_N_RESET] = { 0x3b60, 20 }, ++ [NSS_RST_TX_250M_125M_N_RESET] = { 0x3b60, 21 }, ++ [NSS_QSGMII_TXPI_RST_N_RESET] = { 0x3b60, 22 }, ++ [NSS_QSGMII_CDR_RST_N_RESET] = { 0x3b60, 23 }, ++ [NSS_SGMII2_CDR_RST_N_RESET] = { 0x3b60, 24 }, ++ [NSS_SGMII3_CDR_RST_N_RESET] = { 0x3b60, 25 }, ++ [NSS_CAL_PRBS_RST_N_RESET] = { 0x3b60, 26 }, ++ [NSS_LCKDT_RST_N_RESET] = { 0x3b60, 27 }, ++ [NSS_SRDS_N_RESET] = { 0x3b60, 28 }, + }; + + static const struct regmap_config gcc_ipq806x_regmap_config = { +@@ -2522,6 +3094,8 @@ static int gcc_ipq806x_probe(struct plat + { + struct clk *clk; + struct device *dev = &pdev->dev; ++ struct regmap *regmap; ++ int ret; + + /* Temporary until RPM clocks supported */ + clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 25000000); +@@ -2532,7 +3106,25 @@ static int gcc_ipq806x_probe(struct plat + if (IS_ERR(clk)) + return PTR_ERR(clk); + +- return qcom_cc_probe(pdev, &gcc_ipq806x_desc); ++ ret = qcom_cc_probe(pdev, &gcc_ipq806x_desc); ++ if (ret) ++ return ret; ++ ++ regmap = dev_get_regmap(dev, NULL); ++ if (!regmap) ++ return -ENODEV; ++ ++ /* Setup PLL18 static bits */ ++ regmap_update_bits(regmap, 0x31a4, 0xffffffc0, 0x40000400); ++ regmap_write(regmap, 0x31b0, 0x3080); ++ ++ /* Set GMAC footswitch sleep/wakeup values */ ++ regmap_write(regmap, 0x3cb8, 8); ++ regmap_write(regmap, 0x3cd8, 8); ++ regmap_write(regmap, 0x3cf8, 8); ++ regmap_write(regmap, 0x3d18, 8); ++ ++ return 0; + } + + static int gcc_ipq806x_remove(struct platform_device *pdev) +--- a/include/dt-bindings/clock/qcom,gcc-ipq806x.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq806x.h +@@ -290,5 +290,7 @@ + #define UBI32_CORE1_CLK 279 + #define UBI32_CORE2_CLK 280 + #define EBI2_AON_CLK 281 ++#define NSSTCM_CLK_SRC 282 ++#define NSSTCM_CLK 283 + + #endif +--- a/include/dt-bindings/reset/qcom,gcc-ipq806x.h ++++ b/include/dt-bindings/reset/qcom,gcc-ipq806x.h +@@ -129,4 +129,47 @@ + #define USB30_1_PHY_RESET 112 + #define NSSFB0_RESET 113 + #define NSSFB1_RESET 114 ++#define UBI32_CORE1_CLKRST_CLAMP_RESET 115 ++#define UBI32_CORE1_CLAMP_RESET 116 ++#define UBI32_CORE1_AHB_RESET 117 ++#define UBI32_CORE1_AXI_RESET 118 ++#define UBI32_CORE2_CLKRST_CLAMP_RESET 119 ++#define UBI32_CORE2_CLAMP_RESET 120 ++#define UBI32_CORE2_AHB_RESET 121 ++#define UBI32_CORE2_AXI_RESET 122 ++#define GMAC_CORE1_RESET 123 ++#define GMAC_CORE2_RESET 124 ++#define GMAC_CORE3_RESET 125 ++#define GMAC_CORE4_RESET 126 ++#define GMAC_AHB_RESET 127 ++#define NSS_CH0_RST_RX_CLK_N_RESET 128 ++#define NSS_CH0_RST_TX_CLK_N_RESET 129 ++#define NSS_CH0_RST_RX_125M_N_RESET 130 ++#define NSS_CH0_HW_RST_RX_125M_N_RESET 131 ++#define NSS_CH0_RST_TX_125M_N_RESET 132 ++#define NSS_CH1_RST_RX_CLK_N_RESET 133 ++#define NSS_CH1_RST_TX_CLK_N_RESET 134 ++#define NSS_CH1_RST_RX_125M_N_RESET 135 ++#define NSS_CH1_HW_RST_RX_125M_N_RESET 136 ++#define NSS_CH1_RST_TX_125M_N_RESET 137 ++#define NSS_CH2_RST_RX_CLK_N_RESET 138 ++#define NSS_CH2_RST_TX_CLK_N_RESET 139 ++#define NSS_CH2_RST_RX_125M_N_RESET 140 ++#define NSS_CH2_HW_RST_RX_125M_N_RESET 141 ++#define NSS_CH2_RST_TX_125M_N_RESET 142 ++#define NSS_CH3_RST_RX_CLK_N_RESET 143 ++#define NSS_CH3_RST_TX_CLK_N_RESET 144 ++#define NSS_CH3_RST_RX_125M_N_RESET 145 ++#define NSS_CH3_HW_RST_RX_125M_N_RESET 146 ++#define NSS_CH3_RST_TX_125M_N_RESET 147 ++#define NSS_RST_RX_250M_125M_N_RESET 148 ++#define NSS_RST_TX_250M_125M_N_RESET 149 ++#define NSS_QSGMII_TXPI_RST_N_RESET 150 ++#define NSS_QSGMII_CDR_RST_N_RESET 151 ++#define NSS_SGMII2_CDR_RST_N_RESET 152 ++#define NSS_SGMII3_CDR_RST_N_RESET 153 ++#define NSS_CAL_PRBS_RST_N_RESET 154 ++#define NSS_LCKDT_RST_N_RESET 155 ++#define NSS_SRDS_N_RESET 156 ++ + #endif diff --git a/target/linux/ipq806x/patches-3.18/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch b/target/linux/ipq806x/patches-3.18/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch new file mode 100644 index 0000000..8e59b5c --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch @@ -0,0 +1,105 @@ +From 4f09499bc1d9bb095caccbcd73ff951ee631e521 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 15:42:40 -0700 +Subject: [PATCH 1/8] stmmac: add phy-handle support to the platform layer + +On stmmac driver, PHY specification in device-tree was done using the +non-standard property "snps,phy-addr". Specifying a PHY on a different +MDIO bus that the one within the stmmac controller doesn't seem to be +possible when device-tree is used. + +This change adds support for the phy-handle property, as specified in +Documentation/devicetree/bindings/net/ethernet.txt. + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 28 ++++++++++++++-------- + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 6 ++++- + include/linux/stmmac.h | 1 + + 3 files changed, 24 insertions(+), 11 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +@@ -52,6 +52,7 @@ + #include "stmmac_ptp.h" + #include "stmmac.h" + #include ++#include + + #define STMMAC_ALIGN(x) L1_CACHE_ALIGN(x) + +@@ -818,18 +819,25 @@ static int stmmac_init_phy(struct net_de + priv->speed = 0; + priv->oldduplex = -1; + +- if (priv->plat->phy_bus_name) +- snprintf(bus_id, MII_BUS_ID_SIZE, "%s-%x", +- priv->plat->phy_bus_name, priv->plat->bus_id); +- else +- snprintf(bus_id, MII_BUS_ID_SIZE, "stmmac-%x", +- priv->plat->bus_id); +- +- snprintf(phy_id_fmt, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, bus_id, +- priv->plat->phy_addr); +- pr_debug("stmmac_init_phy: trying to attach to %s\n", phy_id_fmt); ++ if (priv->plat->phy_node) { ++ phydev = of_phy_connect(dev, priv->plat->phy_node, ++ &stmmac_adjust_link, 0, interface); ++ } else { ++ if (priv->plat->phy_bus_name) ++ snprintf(bus_id, MII_BUS_ID_SIZE, "%s-%x", ++ priv->plat->phy_bus_name, priv->plat->bus_id); ++ else ++ snprintf(bus_id, MII_BUS_ID_SIZE, "stmmac-%x", ++ priv->plat->bus_id); ++ ++ snprintf(phy_id_fmt, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, bus_id, ++ priv->plat->phy_addr); ++ pr_debug("stmmac_init_phy: trying to attach to %s\n", ++ phy_id_fmt); + +- phydev = phy_connect(dev, phy_id_fmt, &stmmac_adjust_link, interface); ++ phydev = phy_connect(dev, phy_id_fmt, &stmmac_adjust_link, ++ interface); ++ } + + if (IS_ERR(phydev)) { + pr_err("%s: Could not attach to PHY\n", dev->name); +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include "stmmac.h" + + static const struct of_device_id stmmac_dt_ids[] = { +@@ -155,13 +156,16 @@ static int stmmac_probe_config_dt(struct + /* Default to phy auto-detection */ + plat->phy_addr = -1; + ++ /* If we find a phy-handle property, use it as the PHY */ ++ plat->phy_node = of_parse_phandle(np, "phy-handle", 0); ++ + /* "snps,phy-addr" is not a standard property. Mark it as deprecated + * and warn of its use. Remove this when phy node support is added. + */ + if (of_property_read_u32(np, "snps,phy-addr", &plat->phy_addr) == 0) + dev_warn(&pdev->dev, "snps,phy-addr property is deprecated\n"); + +- if (plat->phy_bus_name) ++ if (plat->phy_node || plat->phy_bus_name) + plat->mdio_bus_data = NULL; + else + plat->mdio_bus_data = +--- a/include/linux/stmmac.h ++++ b/include/linux/stmmac.h +@@ -99,6 +99,7 @@ struct plat_stmmacenet_data { + int phy_addr; + int interface; + struct stmmac_mdio_bus_data *mdio_bus_data; ++ struct device_node *phy_node; + struct stmmac_dma_cfg *dma_cfg; + int clk_csr; + int has_gmac; diff --git a/target/linux/ipq806x/patches-3.18/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch b/target/linux/ipq806x/patches-3.18/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch new file mode 100644 index 0000000..e8f9b5c --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch @@ -0,0 +1,65 @@ +From 0149d275415cd1b2382ce94e5eb32641590097d0 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 15:57:12 -0700 +Subject: [PATCH 2/8] stmmac: move error path at the end of + stmmac_probe_config_dt() + +We will want to do additional clean-up on certain errors. Therefore, +this change moves the error path at the end of the function for better +code readability. + +This patch doesn't change anything functionally. + +Signed-off-by: Mathieu Olivari +--- + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 22 ++++++++++++++++------ + 1 file changed, 16 insertions(+), 6 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -117,13 +117,18 @@ static int stmmac_probe_config_dt(struct + struct device_node *np = pdev->dev.of_node; + struct stmmac_dma_cfg *dma_cfg; + const struct of_device_id *device; ++ int ret; + +- if (!np) +- return -ENODEV; ++ if (!np) { ++ ret = -ENODEV; ++ goto err; ++ } + + device = of_match_device(stmmac_dt_ids, &pdev->dev); +- if (!device) +- return -ENODEV; ++ if (!device) { ++ ret = -ENODEV; ++ goto err; ++ } + + if (device->data) { + const struct stmmac_of_data *data = device->data; +@@ -219,8 +224,10 @@ static int stmmac_probe_config_dt(struct + if (of_find_property(np, "snps,pbl", NULL)) { + dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*dma_cfg), + GFP_KERNEL); +- if (!dma_cfg) +- return -ENOMEM; ++ if (!dma_cfg) { ++ ret = -ENOMEM; ++ goto err; ++ } + plat->dma_cfg = dma_cfg; + of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); + dma_cfg->fixed_burst = +@@ -235,6 +242,9 @@ static int stmmac_probe_config_dt(struct + } + + return 0; ++ ++err: ++ return ret; + } + #else + static int stmmac_probe_config_dt(struct platform_device *pdev, diff --git a/target/linux/ipq806x/patches-3.18/703-stmmac-add-fixed-link-device-tree-support.patch b/target/linux/ipq806x/patches-3.18/703-stmmac-add-fixed-link-device-tree-support.patch new file mode 100644 index 0000000..3c20fe4 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/703-stmmac-add-fixed-link-device-tree-support.patch @@ -0,0 +1,64 @@ +From 3a95f75867be562cb919ff23a738f70357188fbd Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 16:02:03 -0700 +Subject: [PATCH 3/8] stmmac: add fixed-link device-tree support + +In case DT is used, this change adds the ability to the stmmac driver to +detect a fixed-link PHY, instanciate it, and use it during +phy_connect(). + +Fixed link PHYs DT usage is described in: +Documentation/devicetree/bindings/net/fixed-link.txt + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- + drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 12 +++++++++++- + 2 files changed, 12 insertions(+), 2 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +@@ -858,7 +858,7 @@ static int stmmac_init_phy(struct net_de + * device as well. + * Note: phydev->phy_id is the result of reading the UID PHY registers. + */ +- if (phydev->phy_id == 0) { ++ if (!priv->plat->phy_node && phydev->phy_id == 0) { + phy_disconnect(phydev); + return -ENODEV; + } +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -164,6 +164,14 @@ static int stmmac_probe_config_dt(struct + /* If we find a phy-handle property, use it as the PHY */ + plat->phy_node = of_parse_phandle(np, "phy-handle", 0); + ++ /* If phy-handle is not specified, check if we have a fixed-phy */ ++ if (!plat->phy_node && of_phy_is_fixed_link(np)) { ++ if ((of_phy_register_fixed_link(np) < 0)) ++ return -ENODEV; ++ ++ plat->phy_node = of_node_get(np); ++ } ++ + /* "snps,phy-addr" is not a standard property. Mark it as deprecated + * and warn of its use. Remove this when phy node support is added. + */ +@@ -226,7 +234,7 @@ static int stmmac_probe_config_dt(struct + GFP_KERNEL); + if (!dma_cfg) { + ret = -ENOMEM; +- goto err; ++ goto err2; + } + plat->dma_cfg = dma_cfg; + of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); +@@ -243,6 +251,8 @@ static int stmmac_probe_config_dt(struct + + return 0; + ++err2: ++ of_node_put(np); + err: + return ret; + } diff --git a/target/linux/ipq806x/patches-3.18/704-stmmac-add-ipq806x-glue-layer.patch b/target/linux/ipq806x/patches-3.18/704-stmmac-add-ipq806x-glue-layer.patch new file mode 100644 index 0000000..fa67404 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/704-stmmac-add-ipq806x-glue-layer.patch @@ -0,0 +1,427 @@ +From 69fb970ad3fe05af7cb99ea78230c69c7ca0d03b Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 16:10:22 -0700 +Subject: [PATCH 4/8] stmmac: add ipq806x glue layer + +The ethernet controller available in IPQ806x is a Synopsys DesignWare +Gigabit MAC IP core, already supported by the stmmac driver. + +This glue layer implements some platform specific settings required to +get the controller working on an IPQ806x based platform. + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/Kconfig | 1 + + drivers/net/ethernet/stmicro/stmmac/Makefile | 2 +- + drivers/net/ethernet/stmicro/stmmac/dwmac-ipq.c | 324 +++++++++++++++++++++ + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 1 + + .../net/ethernet/stmicro/stmmac/stmmac_platform.h | 1 + + 5 files changed, 328 insertions(+), 1 deletion(-) + create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-ipq.c + +--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig ++++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig +@@ -16,6 +16,7 @@ if STMMAC_ETH + config STMMAC_PLATFORM + bool "STMMAC Platform bus support" + depends on STMMAC_ETH ++ select MFD_SYSCON + default y + ---help--- + This selects the platform specific bus support for +@@ -26,6 +27,15 @@ config STMMAC_PLATFORM + + If unsure, say N. + ++config DWMAC_IPQ806X ++ bool "QCA IPQ806x dwmac support" ++ depends on STMMAC_PLATFORM && ARCH_QCOM ++ help ++ Support for Ethernet controller on QCA IPQ806x SoC. ++ ++ This selects the QCA IPQ806x SoC glue layer support for ++ the stmmac device driver. ++ + config DWMAC_MESON + bool "Amlogic Meson dwmac support" + depends on STMMAC_PLATFORM && ARCH_MESON +--- a/drivers/net/ethernet/stmicro/stmmac/Makefile ++++ b/drivers/net/ethernet/stmicro/stmmac/Makefile +@@ -1,6 +1,7 @@ + obj-$(CONFIG_STMMAC_ETH) += stmmac.o + stmmac-$(CONFIG_STMMAC_PLATFORM) += stmmac_platform.o + stmmac-$(CONFIG_STMMAC_PCI) += stmmac_pci.o ++stmmac-$(CONFIG_DWMAC_IPQ806X) += dwmac-ipq806x.o + stmmac-$(CONFIG_DWMAC_MESON) += dwmac-meson.o + stmmac-$(CONFIG_DWMAC_SUNXI) += dwmac-sunxi.o + stmmac-$(CONFIG_DWMAC_STI) += dwmac-sti.o +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -46,6 +46,9 @@ static const struct of_device_id stmmac_ + #ifdef CONFIG_DWMAC_SOCFPGA + { .compatible = "altr,socfpga-stmmac", .data = &socfpga_gmac_data }, + #endif ++#ifdef CONFIG_DWMAC_IPQ806X ++ { .compatible = "qcom,ipq806x-gmac", .data = &ipq806x_gmac_data }, ++#endif + /* SoC specific glue layers should come before generic bindings */ + { .compatible = "st,spear600-gmac"}, + { .compatible = "snps,dwmac-3.610"}, +--- /dev/null ++++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c +@@ -0,0 +1,343 @@ ++/* ++ * Qualcomm Atheros IPQ806x GMAC glue layer ++ * ++ * Copyright (C) 2015 The Linux Foundation ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stmmac.h" ++ ++#define NSS_COMMON_CLK_GATE 0x8 ++#define NSS_COMMON_CLK_GATE_PTP_EN(x) BIT(0x10 + x) ++#define NSS_COMMON_CLK_GATE_RGMII_RX_EN(x) BIT(0x9 + (x * 2)) ++#define NSS_COMMON_CLK_GATE_RGMII_TX_EN(x) BIT(0x8 + (x * 2)) ++#define NSS_COMMON_CLK_GATE_GMII_RX_EN(x) BIT(0x4 + x) ++#define NSS_COMMON_CLK_GATE_GMII_TX_EN(x) BIT(0x0 + x) ++ ++#define NSS_COMMON_CLK_DIV0 0xC ++#define NSS_COMMON_CLK_DIV_OFFSET(x) (x * 8) ++#define NSS_COMMON_CLK_DIV_MASK 0x7f ++ ++#define NSS_COMMON_CLK_SRC_CTRL 0x14 ++#define NSS_COMMON_CLK_SRC_CTRL_OFFSET(x) (1 << x) ++/* Mode is coded on 1 bit but is different depending on the MAC ID: ++ * MAC0: QSGMII=0 RGMII=1 ++ * MAC1: QSGMII=0 SGMII=0 RGMII=1 ++ * MAC2 & MAC3: QSGMII=0 SGMII=1 ++ */ ++#define NSS_COMMON_CLK_SRC_CTRL_RGMII(x) 1 ++#define NSS_COMMON_CLK_SRC_CTRL_SGMII(x) ((x >= 2) ? 1 : 0) ++ ++#define NSS_COMMON_MACSEC_CTL 0x28 ++#define NSS_COMMON_MACSEC_CTL_EXT_BYPASS_EN(x) (1 << x) ++ ++#define NSS_COMMON_GMAC_CTL(x) (0x30 + (x * 4)) ++#define NSS_COMMON_GMAC_CTL_CSYS_REQ BIT(19) ++#define NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL BIT(16) ++#define NSS_COMMON_GMAC_CTL_IFG_LIMIT_OFFSET 8 ++#define NSS_COMMON_GMAC_CTL_IFG_OFFSET 0 ++#define NSS_COMMON_GMAC_CTL_IFG_MASK 0x3f ++ ++#define NSS_COMMON_CLK_DIV_RGMII_1000 1 ++#define NSS_COMMON_CLK_DIV_RGMII_100 9 ++#define NSS_COMMON_CLK_DIV_RGMII_10 99 ++#define NSS_COMMON_CLK_DIV_SGMII_1000 0 ++#define NSS_COMMON_CLK_DIV_SGMII_100 4 ++#define NSS_COMMON_CLK_DIV_SGMII_10 49 ++ ++#define QSGMII_PCS_MODE_CTL 0x68 ++#define QSGMII_PCS_MODE_CTL_AUTONEG_EN(x) BIT((x * 8) + 7) ++ ++#define QSGMII_PCS_CAL_LCKDT_CTL 0x120 ++#define QSGMII_PCS_CAL_LCKDT_CTL_RST BIT(19) ++ ++/* Only GMAC1/2/3 support SGMII and their CTL register are not contiguous */ ++#define QSGMII_PHY_SGMII_CTL(x) ((x == 1) ? 0x134 : \ ++ (0x13c + (4 * (x - 2)))) ++#define QSGMII_PHY_CDR_EN BIT(0) ++#define QSGMII_PHY_RX_FRONT_EN BIT(1) ++#define QSGMII_PHY_RX_SIGNAL_DETECT_EN BIT(2) ++#define QSGMII_PHY_TX_DRIVER_EN BIT(3) ++#define QSGMII_PHY_QSGMII_EN BIT(7) ++#define QSGMII_PHY_PHASE_LOOP_GAIN_OFFSET 12 ++#define QSGMII_PHY_PHASE_LOOP_GAIN_MASK 0x7 ++#define QSGMII_PHY_RX_DC_BIAS_OFFSET 18 ++#define QSGMII_PHY_RX_DC_BIAS_MASK 0x3 ++#define QSGMII_PHY_RX_INPUT_EQU_OFFSET 20 ++#define QSGMII_PHY_RX_INPUT_EQU_MASK 0x3 ++#define QSGMII_PHY_CDR_PI_SLEW_OFFSET 22 ++#define QSGMII_PHY_CDR_PI_SLEW_MASK 0x3 ++#define QSGMII_PHY_TX_DRV_AMP_OFFSET 28 ++#define QSGMII_PHY_TX_DRV_AMP_MASK 0xf ++ ++struct ipq806x_gmac { ++ struct platform_device *pdev; ++ struct regmap *nss_common; ++ struct regmap *qsgmii_csr; ++ uint32_t id; ++ struct clk *core_clk; ++ phy_interface_t phy_mode; ++}; ++ ++static int get_clk_div_sgmii(struct ipq806x_gmac *gmac, unsigned int speed) ++{ ++ struct device *dev = &gmac->pdev->dev; ++ int div; ++ ++ switch (speed) { ++ case SPEED_1000: ++ div = NSS_COMMON_CLK_DIV_SGMII_1000; ++ break; ++ ++ case SPEED_100: ++ div = NSS_COMMON_CLK_DIV_SGMII_100; ++ break; ++ ++ case SPEED_10: ++ div = NSS_COMMON_CLK_DIV_SGMII_10; ++ break; ++ ++ default: ++ dev_err(dev, "Speed %dMbps not supported in SGMII\n", speed); ++ return -EINVAL; ++ } ++ ++ return div; ++} ++ ++static int get_clk_div_rgmii(struct ipq806x_gmac *gmac, unsigned int speed) ++{ ++ struct device *dev = &gmac->pdev->dev; ++ int div; ++ ++ switch (speed) { ++ case SPEED_1000: ++ div = NSS_COMMON_CLK_DIV_RGMII_1000; ++ break; ++ ++ case SPEED_100: ++ div = NSS_COMMON_CLK_DIV_RGMII_100; ++ break; ++ ++ case SPEED_10: ++ div = NSS_COMMON_CLK_DIV_RGMII_10; ++ break; ++ ++ default: ++ dev_err(dev, "Speed %dMbps not supported in RGMII\n", speed); ++ return -EINVAL; ++ } ++ ++ return div; ++} ++ ++static int ipq806x_gmac_set_speed(struct ipq806x_gmac *gmac, unsigned int speed) ++{ ++ uint32_t clk_bits, val; ++ int div; ++ ++ switch (gmac->phy_mode) { ++ case PHY_INTERFACE_MODE_RGMII: ++ div = get_clk_div_rgmii(gmac, speed); ++ clk_bits = NSS_COMMON_CLK_GATE_RGMII_RX_EN(gmac->id) | ++ NSS_COMMON_CLK_GATE_RGMII_TX_EN(gmac->id); ++ break; ++ ++ case PHY_INTERFACE_MODE_SGMII: ++ div = get_clk_div_sgmii(gmac, speed); ++ clk_bits = NSS_COMMON_CLK_GATE_GMII_RX_EN(gmac->id) | ++ NSS_COMMON_CLK_GATE_GMII_TX_EN(gmac->id); ++ break; ++ ++ default: ++ dev_err(&gmac->pdev->dev, "Unsupported PHY mode: \"%s\"\n", ++ phy_modes(gmac->phy_mode)); ++ return -EINVAL; ++ } ++ ++ /* Disable the clocks */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val); ++ val &= ~clk_bits; ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val); ++ ++ /* Set the divider */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_DIV0, &val); ++ val &= ~(NSS_COMMON_CLK_DIV_MASK ++ << NSS_COMMON_CLK_DIV_OFFSET(gmac->id)); ++ val |= div << NSS_COMMON_CLK_DIV_OFFSET(gmac->id); ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_DIV0, val); ++ ++ /* Enable the clock back */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val); ++ val |= clk_bits; ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val); ++ ++ return 0; ++} ++ ++static void *ipq806x_gmac_of_parse(struct ipq806x_gmac *gmac) ++{ ++ struct device *dev = &gmac->pdev->dev; ++ ++ gmac->phy_mode = of_get_phy_mode(dev->of_node); ++ if (gmac->phy_mode < 0) { ++ dev_err(dev, "missing phy mode property\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ if (of_property_read_u32(dev->of_node, "qcom,id", &gmac->id) < 0) { ++ dev_err(dev, "missing qcom id property\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ /* The GMACs are called 1 to 4 in the documentation, but to simplify the ++ * code and keep it consistent with the Linux convention, we'll number ++ * them from 0 to 3 here. ++ */ ++ if (gmac->id < 0 || gmac->id > 3) { ++ dev_err(dev, "invalid gmac id\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ gmac->core_clk = devm_clk_get(dev, "stmmaceth"); ++ if (IS_ERR(gmac->core_clk)) { ++ dev_err(dev, "missing stmmaceth clk property\n"); ++ return gmac->core_clk; ++ } ++ clk_set_rate(gmac->core_clk, 266000000); ++ ++ /* Setup the register map for the nss common registers */ ++ gmac->nss_common = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "qcom,nss-common"); ++ if (IS_ERR(gmac->nss_common)) { ++ dev_err(dev, "missing nss-common node\n"); ++ return gmac->nss_common; ++ } ++ ++ /* Setup the register map for the qsgmii csr registers */ ++ gmac->qsgmii_csr = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "qcom,qsgmii-csr"); ++ if (IS_ERR(gmac->qsgmii_csr)) { ++ dev_err(dev, "missing qsgmii-csr node\n"); ++ return gmac->qsgmii_csr; ++ } ++ ++ return NULL; ++} ++ ++static void *ipq806x_gmac_setup(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct ipq806x_gmac *gmac; ++ int val; ++ void *err; ++ ++ gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL); ++ if (!gmac) ++ return ERR_PTR(-ENOMEM); ++ ++ gmac->pdev = pdev; ++ ++ err = ipq806x_gmac_of_parse(gmac); ++ if (err) { ++ dev_err(dev, "device tree parsing error\n"); ++ return err; ++ } ++ ++ regmap_write(gmac->qsgmii_csr, QSGMII_PCS_CAL_LCKDT_CTL, ++ QSGMII_PCS_CAL_LCKDT_CTL_RST); ++ ++ /* Inter frame gap is set to 12 */ ++ val = 12 << NSS_COMMON_GMAC_CTL_IFG_OFFSET | ++ 12 << NSS_COMMON_GMAC_CTL_IFG_LIMIT_OFFSET; ++ /* We also initiate an AXI low power exit request */ ++ val |= NSS_COMMON_GMAC_CTL_CSYS_REQ; ++ switch (gmac->phy_mode) { ++ case PHY_INTERFACE_MODE_RGMII: ++ val |= NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL; ++ break; ++ case PHY_INTERFACE_MODE_SGMII: ++ val &= ~NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL; ++ break; ++ default: ++ dev_err(&pdev->dev, "Unsupported PHY mode: \"%s\"\n", ++ phy_modes(gmac->phy_mode)); ++ return NULL; ++ } ++ regmap_write(gmac->nss_common, NSS_COMMON_GMAC_CTL(gmac->id), val); ++ ++ /* Configure the clock src according to the mode */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_SRC_CTRL, &val); ++ val &= ~NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id); ++ switch (gmac->phy_mode) { ++ case PHY_INTERFACE_MODE_RGMII: ++ val |= NSS_COMMON_CLK_SRC_CTRL_RGMII(gmac->id) << ++ NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id); ++ break; ++ case PHY_INTERFACE_MODE_SGMII: ++ val |= NSS_COMMON_CLK_SRC_CTRL_SGMII(gmac->id) << ++ NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id); ++ break; ++ default: ++ dev_err(&pdev->dev, "Unsupported PHY mode: \"%s\"\n", ++ phy_modes(gmac->phy_mode)); ++ return NULL; ++ } ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_SRC_CTRL, val); ++ ++ /* Enable PTP clock */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val); ++ val |= NSS_COMMON_CLK_GATE_PTP_EN(gmac->id); ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val); ++ ++ if (gmac->phy_mode == PHY_INTERFACE_MODE_SGMII) { ++ regmap_write(gmac->qsgmii_csr, QSGMII_PHY_SGMII_CTL(gmac->id), ++ QSGMII_PHY_CDR_EN | ++ QSGMII_PHY_RX_FRONT_EN | ++ QSGMII_PHY_RX_SIGNAL_DETECT_EN | ++ QSGMII_PHY_TX_DRIVER_EN | ++ QSGMII_PHY_QSGMII_EN | ++ 0x4 << QSGMII_PHY_PHASE_LOOP_GAIN_OFFSET | ++ 0x3 << QSGMII_PHY_RX_DC_BIAS_OFFSET | ++ 0x1 << QSGMII_PHY_RX_INPUT_EQU_OFFSET | ++ 0x2 << QSGMII_PHY_CDR_PI_SLEW_OFFSET | ++ 0xC << QSGMII_PHY_TX_DRV_AMP_OFFSET); ++ } ++ ++ return gmac; ++} ++ ++static void ipq806x_gmac_fix_mac_speed(void *priv, unsigned int speed) ++{ ++ struct ipq806x_gmac *gmac = priv; ++ ++ ipq806x_gmac_set_speed(gmac, speed); ++} ++ ++const struct stmmac_of_data ipq806x_gmac_data = { ++ .has_gmac = 1, ++ .setup = ipq806x_gmac_setup, ++ .fix_mac_speed = ipq806x_gmac_fix_mac_speed, ++}; +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h +@@ -137,6 +137,9 @@ void stmmac_disable_eee_mode(struct stmm + bool stmmac_eee_init(struct stmmac_priv *priv); + + #ifdef CONFIG_STMMAC_PLATFORM ++#ifdef CONFIG_DWMAC_IPQ806X ++extern const struct stmmac_of_data ipq806x_gmac_data; ++#endif + #ifdef CONFIG_DWMAC_MESON + extern const struct stmmac_of_data meson6_dwmac_data; + #endif diff --git a/target/linux/ipq806x/patches-3.18/705-net-stmmac-ipq806x-document-device-tree-bindings.patch b/target/linux/ipq806x/patches-3.18/705-net-stmmac-ipq806x-document-device-tree-bindings.patch new file mode 100644 index 0000000..3144fa3 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/705-net-stmmac-ipq806x-document-device-tree-bindings.patch @@ -0,0 +1,52 @@ +From 0f9605d9409b77a89daef91cc68239fc2ff50457 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 16:51:25 -0700 +Subject: [PATCH 5/8] net: stmmac: ipq806x: document device tree bindings + +Add the device tree bindings documentation for the QCA IPQ806x +variant of the Synopsys DesignWare MAC. + +Signed-off-by: Mathieu Olivari +--- + .../devicetree/bindings/net/ipq806x-dwmac.txt | 35 ++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + create mode 100644 Documentation/devicetree/bindings/net/ipq806x-dwmac.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/net/ipq806x-dwmac.txt +@@ -0,0 +1,35 @@ ++* IPQ806x DWMAC Ethernet controller ++ ++The device inherits all the properties of the dwmac/stmmac devices ++described in the file net/stmmac.txt with the following changes. ++ ++Required properties: ++ ++- compatible: should be "qcom,ipq806x-gmac" along with "snps,dwmac" ++ and any applicable more detailed version number ++ described in net/stmmac.txt ++ ++- qcom,nss-common: should contain a phandle to a syscon device mapping the ++ nss-common registers. ++ ++- qcom,qsgmii-csr: should contain a phandle to a syscon device mapping the ++ qsgmii-csr registers. ++ ++Example: ++ ++ gmac: ethernet@37000000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37000000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE1_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE1_RESET>; ++ reset-names = "stmmaceth"; ++ }; diff --git a/target/linux/ipq806x/patches-3.18/706-net-stmmac-create-one-debugfs-dir-per-net-device.patch b/target/linux/ipq806x/patches-3.18/706-net-stmmac-create-one-debugfs-dir-per-net-device.patch new file mode 100644 index 0000000..50127fd --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/706-net-stmmac-create-one-debugfs-dir-per-net-device.patch @@ -0,0 +1,171 @@ +From df944689d491e6af533173bf2ef448c3dd334f15 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Mon, 11 May 2015 15:15:25 -0700 +Subject: [PATCH 6/8] net: stmmac: create one debugfs dir per net-device + +stmmac DebugFS entries are currently global to the driver. As a result, +having more than one stmmac device in the system creates the following +error: +* ERROR stmmaceth, debugfs create directory failed +* stmmac_hw_setup: failed debugFS registration + +This also results in being able to access the debugfs information for +the first registered device only. + +This patch changes the debugfs structure to have one sub-directory per +net-device. Files under "/sys/kernel/debug/stmmaceth" will now show-up +under /sys/kernel/debug/stmmaceth/ethN/. + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/stmmac.h | 6 ++ + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 76 ++++++++++++++++------- + 2 files changed, 59 insertions(+), 23 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac.h ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac.h +@@ -116,6 +116,12 @@ struct stmmac_priv { + int use_riwt; + int irq_wake; + spinlock_t ptp_lock; ++ ++#ifdef CONFIG_DEBUG_FS ++ struct dentry *dbgfs_dir; ++ struct dentry *dbgfs_rings_status; ++ struct dentry *dbgfs_dma_cap; ++#endif + }; + + int stmmac_mdio_unregister(struct net_device *ndev); +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +@@ -119,7 +119,7 @@ static irqreturn_t stmmac_interrupt(int + + #ifdef CONFIG_STMMAC_DEBUG_FS + static int stmmac_init_fs(struct net_device *dev); +-static void stmmac_exit_fs(void); ++static void stmmac_exit_fs(struct net_device *dev); + #endif + + #define STMMAC_COAL_TIMER(x) (jiffies + usecs_to_jiffies(x)) +@@ -1879,7 +1879,7 @@ static int stmmac_release(struct net_dev + netif_carrier_off(dev); + + #ifdef CONFIG_STMMAC_DEBUG_FS +- stmmac_exit_fs(); ++ stmmac_exit_fs(dev); + #endif + + stmmac_release_ptp(priv); +@@ -2467,8 +2467,6 @@ static int stmmac_ioctl(struct net_devic + + #ifdef CONFIG_STMMAC_DEBUG_FS + static struct dentry *stmmac_fs_dir; +-static struct dentry *stmmac_rings_status; +-static struct dentry *stmmac_dma_cap; + + static void sysfs_display_ring(void *head, int size, int extend_desc, + struct seq_file *seq) +@@ -2607,36 +2605,39 @@ static const struct file_operations stmm + + static int stmmac_init_fs(struct net_device *dev) + { +- /* Create debugfs entries */ +- stmmac_fs_dir = debugfs_create_dir(STMMAC_RESOURCE_NAME, NULL); ++ struct stmmac_priv *priv = netdev_priv(dev); ++ ++ /* Create per netdev entries */ ++ priv->dbgfs_dir = debugfs_create_dir(dev->name, stmmac_fs_dir); + +- if (!stmmac_fs_dir || IS_ERR(stmmac_fs_dir)) { +- pr_err("ERROR %s, debugfs create directory failed\n", +- STMMAC_RESOURCE_NAME); ++ if (!priv->dbgfs_dir || IS_ERR(priv->dbgfs_dir)) { ++ pr_err("ERROR %s/%s, debugfs create directory failed\n", ++ STMMAC_RESOURCE_NAME, dev->name); + + return -ENOMEM; + } + + /* Entry to report DMA RX/TX rings */ +- stmmac_rings_status = debugfs_create_file("descriptors_status", +- S_IRUGO, stmmac_fs_dir, dev, +- &stmmac_rings_status_fops); ++ priv->dbgfs_rings_status = ++ debugfs_create_file("descriptors_status", S_IRUGO, ++ priv->dbgfs_dir, dev, ++ &stmmac_rings_status_fops); + +- if (!stmmac_rings_status || IS_ERR(stmmac_rings_status)) { ++ if (!priv->dbgfs_rings_status || IS_ERR(priv->dbgfs_rings_status)) { + pr_info("ERROR creating stmmac ring debugfs file\n"); +- debugfs_remove(stmmac_fs_dir); ++ debugfs_remove_recursive(priv->dbgfs_dir); + + return -ENOMEM; + } + + /* Entry to report the DMA HW features */ +- stmmac_dma_cap = debugfs_create_file("dma_cap", S_IRUGO, stmmac_fs_dir, +- dev, &stmmac_dma_cap_fops); ++ priv->dbgfs_dma_cap = debugfs_create_file("dma_cap", S_IRUGO, ++ priv->dbgfs_dir, ++ dev, &stmmac_dma_cap_fops); + +- if (!stmmac_dma_cap || IS_ERR(stmmac_dma_cap)) { ++ if (!priv->dbgfs_dma_cap || IS_ERR(priv->dbgfs_dma_cap)) { + pr_info("ERROR creating stmmac MMC debugfs file\n"); +- debugfs_remove(stmmac_rings_status); +- debugfs_remove(stmmac_fs_dir); ++ debugfs_remove_recursive(priv->dbgfs_dir); + + return -ENOMEM; + } +@@ -2644,11 +2645,11 @@ static int stmmac_init_fs(struct net_dev + return 0; + } + +-static void stmmac_exit_fs(void) ++static void stmmac_exit_fs(struct net_device *dev) + { +- debugfs_remove(stmmac_rings_status); +- debugfs_remove(stmmac_dma_cap); +- debugfs_remove(stmmac_fs_dir); ++ struct stmmac_priv *priv = netdev_priv(dev); ++ ++ debugfs_remove_recursive(priv->dbgfs_dir); + } + #endif /* CONFIG_STMMAC_DEBUG_FS */ + +@@ -3032,6 +3033,21 @@ static int __init stmmac_init(void) + ret = stmmac_register_pci(); + if (ret) + goto err_pci; ++ ++#ifdef CONFIG_STMMAC_DEBUG_FS ++ /* Create debugfs main directory if it doesn't exist yet */ ++ if (stmmac_fs_dir == NULL) { ++ stmmac_fs_dir = debugfs_create_dir(STMMAC_RESOURCE_NAME, NULL); ++ ++ if (!stmmac_fs_dir || IS_ERR(stmmac_fs_dir)) { ++ pr_err("ERROR %s, debugfs create directory failed\n", ++ STMMAC_RESOURCE_NAME); ++ ++ return -ENOMEM; ++ } ++ } ++#endif ++ + return 0; + err_pci: + stmmac_unregister_platform(); +@@ -3042,6 +3058,9 @@ err: + + static void __exit stmmac_exit(void) + { ++#ifdef CONFIG_STMMAC_DEBUG_FS ++ debugfs_remove_recursive(stmmac_fs_dir); ++#endif + stmmac_unregister_platform(); + stmmac_unregister_pci(); + } diff --git a/target/linux/ipq806x/patches-3.18/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch b/target/linux/ipq806x/patches-3.18/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch new file mode 100644 index 0000000..fe28942 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch @@ -0,0 +1,146 @@ +From e81de9d28bd0421c236df322872e64edf4ee1852 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Mon, 11 May 2015 16:32:09 -0700 +Subject: [PATCH 7/8] ARM: dts: qcom: add mdio nodes to ap148 & db149 + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 40 ++++++++++++++++++++++++++- + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 46 ++++++++++++++++++++++++++++++++ + 2 files changed, 85 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -19,8 +19,9 @@ + }; + }; + +- alias { ++ aliases { + serial0 = &uart4; ++ mdio-gpio0 = &mdio0; + }; + + chosen { +@@ -86,6 +87,15 @@ + bias-bus-hold; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -184,6 +194,34 @@ + + linux,part-probe = "qcom-smem"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -16,6 +16,7 @@ + + alias { + serial0 = &uart2; ++ mdio-gpio0 = &mdio0; + }; + + chosen { +@@ -65,6 +66,15 @@ + bias-none; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi2: gsbi@12480000 { +@@ -176,5 +186,44 @@ + pinctrl-0 = <&pcie2_pins>; + pinctrl-names = "default"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ ++ phy6: ethernet-phy@6 { ++ device_type = "ethernet-phy"; ++ reg = <6>; ++ }; ++ ++ phy7: ethernet-phy@7 { ++ device_type = "ethernet-phy"; ++ reg = <7>; ++ }; ++ }; + }; + }; diff --git a/target/linux/ipq806x/patches-3.18/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-3.18/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch new file mode 100644 index 0000000..3455d45 --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch @@ -0,0 +1,212 @@ +From cab1f4720e82f2e17eaeed9a9ad9e4f07c742977 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Mon, 11 May 2015 12:29:18 -0700 +Subject: [PATCH 8/8] ARM: dts: qcom: add gmac nodes to ipq806x platforms + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 31 ++++++++++++ + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 43 ++++++++++++++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 86 ++++++++++++++++++++++++++++++++ + 3 files changed, 160 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -96,6 +96,16 @@ + bias-disable; + }; + }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -222,6 +232,27 @@ + reg = <4>; + }; + }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ phy-handle = <&phy4>; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -75,6 +75,14 @@ + bias-disable; + }; + }; ++ ++ rgmii0_pins: rgmii0_pins { ++ mux { ++ pins = "gpio2", "gpio66"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi2: gsbi@12480000 { +@@ -225,5 +233,40 @@ + reg = <7>; + }; + }; ++ ++ gmac0: ethernet@37000000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ qcom,id = <0>; ++ phy-handle = <&phy4>; ++ ++ pinctrl-0 = <&rgmii0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <1>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ phy-handle = <&phy6>; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <3>; ++ phy-handle = <&phy7>; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -698,6 +698,92 @@ + status = "disabled"; + }; + ++ nss_common: syscon@03000000 { ++ compatible = "syscon"; ++ reg = <0x03000000 0x0000FFFF>; ++ }; ++ ++ qsgmii_csr: syscon@1bb00000 { ++ compatible = "syscon"; ++ reg = <0x1bb00000 0x000001FF>; ++ }; ++ ++ gmac0: ethernet@37000000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37000000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE1_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE1_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37200000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE2_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE2_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37400000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE3_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE3_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37600000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE4_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE4_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-3.18/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch b/target/linux/ipq806x/patches-3.18/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch new file mode 100644 index 0000000..68222ce --- /dev/null +++ b/target/linux/ipq806x/patches-3.18/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch @@ -0,0 +1,35 @@ +From 5bf2dabde1fa3af0c9082b42b6847ef3fd198b13 Mon Sep 17 00:00:00 2001 +From: Jonas Gorski +Date: Sun, 9 Aug 2015 12:53:55 +0200 +Subject: [PATCH] stmac: platform: add support for retreiving mac from mtd + +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -284,6 +284,7 @@ static int stmmac_pltfr_probe(struct pla + struct stmmac_priv *priv = NULL; + struct plat_stmmacenet_data *plat_dat = NULL; + const char *mac = NULL; ++ u8 mtd_mac[ETH_ALEN] = { }; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + addr = devm_ioremap_resource(dev, res); +@@ -313,6 +314,15 @@ static int stmmac_pltfr_probe(struct pla + pr_err("%s: main dt probe failed", __func__); + return ret; + } ++ ++ if (!mac) { ++ ret = of_get_mac_address_mtd(dev->of_node, &mtd_mac); ++ if (ret == -EPROBE_DEFER) ++ return ret; ++ ++ if (is_valid_ether_addr(&mtd_mac)) ++ mac = mtd_mac; ++ } + } + + /* Custom setup (if needed) */ diff --git a/target/linux/ipq806x/patches-4.1/020-add-ap148-bootargs.patch b/target/linux/ipq806x/patches-4.1/020-add-ap148-bootargs.patch new file mode 100644 index 0000000..c0ad74c --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/020-add-ap148-bootargs.patch @@ -0,0 +1,46 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -14,6 +14,14 @@ + }; + }; + ++ alias { ++ serial0 = &uart4; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ + soc { + pinmux@800000 { + i2c4_pins: i2c4_pinmux { +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -159,7 +159,7 @@ + + syscon-tcsr = <&tcsr>; + +- serial@12490000 { ++ uart2: serial@12490000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x12490000 0x1000>, + <0x12480000 0x1000>; +@@ -197,7 +197,7 @@ + + syscon-tcsr = <&tcsr>; + +- serial@16340000 { ++ uart4: serial@16340000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x16340000 0x1000>, + <0x16300000 0x1000>; +@@ -234,7 +234,7 @@ + + syscon-tcsr = <&tcsr>; + +- serial@1a240000 { ++ uart5: serial@1a240000 { + compatible = "qcom,msm-uartdm-v1.3", "qcom,msm-uartdm"; + reg = <0x1a240000 0x1000>, + <0x1a200000 0x1000>; diff --git a/target/linux/ipq806x/patches-4.1/021-add-ap148-partitions.patch b/target/linux/ipq806x/patches-4.1/021-add-ap148-partitions.patch new file mode 100644 index 0000000..bfdb30f --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/021-add-ap148-partitions.patch @@ -0,0 +1,19 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -77,15 +77,7 @@ + spi-max-frequency = <50000000>; + reg = <0>; + +- partition@0 { +- label = "rootfs"; +- reg = <0x0 0x1000000>; +- }; +- +- partition@1 { +- label = "scratch"; +- reg = <0x1000000 0x1000000>; +- }; ++ linux,part-probe = "qcom-smem"; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.1/022-add-db149-dts.patch b/target/linux/ipq806x/patches-4.1/022-add-db149-dts.patch new file mode 100644 index 0000000..7d8c8e8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/022-add-db149-dts.patch @@ -0,0 +1,160 @@ +From f26cc3733bdd697bd81ae505fc133fa7c9b6ea19 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Tue, 7 Apr 2015 19:58:58 -0700 +Subject: [PATCH] ARM: dts: qcom: add initial DB149 device-tree + +Add basic DB149 (IPQ806x based platform) device-tree. It supports UART, +SATA, USB2, USB3 and NOR flash. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 132 +++++++++++++++++++++++++++++++ + 2 files changed, 133 insertions(+) + create mode 100644 arch/arm/boot/dts/qcom-ipq8064-db149.dts + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -451,6 +451,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \ + qcom-apq8084-ifc6540.dtb \ + qcom-apq8084-mtp.dtb \ + qcom-ipq8064-ap148.dtb \ ++ qcom-ipq8064-db149.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -0,0 +1,132 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++/ { ++ model = "Qualcomm IPQ8064/DB149"; ++ compatible = "qcom,ipq8064-db149", "qcom,ipq8064"; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ alias { ++ serial0 = &uart2; ++ }; ++ ++ chosen { ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ spi_pins: spi_pins { ++ mux { ++ pins = "gpio18", "gpio19", "gpio21"; ++ function = "gsbi5"; ++ drive-strength = <10>; ++ bias-none; ++ }; ++ }; ++ }; ++ ++ gsbi2: gsbi@12480000 { ++ qcom,mode = ; ++ status = "ok"; ++ uart2: serial@12490000 { ++ status = "ok"; ++ }; ++ }; ++ ++ gsbi5: gsbi@1a200000 { ++ qcom,mode = ; ++ status = "ok"; ++ ++ spi4: spi@1a280000 { ++ status = "ok"; ++ spi-max-frequency = <50000000>; ++ ++ pinctrl-0 = <&spi_pins>; ++ pinctrl-names = "default"; ++ ++ cs-gpios = <&qcom_pinmux 20 0>; ++ ++ flash: m25p80@0 { ++ compatible = "s25fl256s1"; ++ #address-cells = <1>; ++ #size-cells = <1>; ++ spi-max-frequency = <50000000>; ++ reg = <0>; ++ m25p,fast-read; ++ ++ partition@0 { ++ label = "lowlevel_init"; ++ reg = <0x0 0x1b0000>; ++ }; ++ ++ partition@1 { ++ label = "u-boot"; ++ reg = <0x1b0000 0x80000>; ++ }; ++ ++ partition@2 { ++ label = "u-boot-env"; ++ reg = <0x230000 0x40000>; ++ }; ++ ++ partition@3 { ++ label = "caldata"; ++ reg = <0x270000 0x40000>; ++ }; ++ ++ partition@4 { ++ label = "firmware"; ++ reg = <0x2b0000 0x1d50000>; ++ }; ++ }; ++ }; ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ }; ++}; diff --git a/target/linux/ipq806x/patches-4.1/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch b/target/linux/ipq806x/patches-4.1/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch new file mode 100644 index 0000000..6b29637 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/023-ARM-dts-ipq806x-Disable-i2c-device-on-gsbi4.patch @@ -0,0 +1,53 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -46,15 +46,12 @@ + serial@16340000 { + status = "ok"; + }; +- +- i2c4: i2c@16380000 { +- status = "ok"; +- +- clock-frequency = <200000>; +- +- pinctrl-0 = <&i2c4_pins>; +- pinctrl-names = "default"; +- }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ + }; + + gsbi5: gsbi@1a200000 { +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -807,7 +807,7 @@ static struct clk_rcg gsbi7_qup_src = { + .parent_names = gcc_pxo_pll8, + .num_parents = 2, + .ops = &clk_rcg_ops, +- .flags = CLK_SET_PARENT_GATE, ++ .flags = CLK_SET_PARENT_GATE | CLK_IGNORE_UNUSED, + }, + }, + }; +@@ -823,7 +823,7 @@ static struct clk_branch gsbi7_qup_clk = + .parent_names = (const char *[]){ "gsbi7_qup_src" }, + .num_parents = 1, + .ops = &clk_branch_ops, +- .flags = CLK_SET_RATE_PARENT, ++ .flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED, + }, + }, + }; +@@ -871,7 +871,7 @@ static struct clk_branch gsbi4_h_clk = { + .hw.init = &(struct clk_init_data){ + .name = "gsbi4_h_clk", + .ops = &clk_branch_ops, +- .flags = CLK_IS_ROOT, ++ .flags = CLK_IS_ROOT | CLK_IGNORE_UNUSED, + }, + }, + }; diff --git a/target/linux/ipq806x/patches-4.1/024-ap148-add-memory-node.patch b/target/linux/ipq806x/patches-4.1/024-ap148-add-memory-node.patch new file mode 100644 index 0000000..f026ed9 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/024-ap148-add-memory-node.patch @@ -0,0 +1,14 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -4,6 +4,11 @@ + model = "Qualcomm IPQ8064/AP148"; + compatible = "qcom,ipq8064-ap148", "qcom,ipq8064"; + ++ memory@0 { ++ reg = <0x42000000 0x1e000000>; ++ device_type = "memory"; ++ }; ++ + reserved-memory { + #address-cells = <1>; + #size-cells = <1>; diff --git a/target/linux/ipq806x/patches-4.1/030-hwspinlock-core-add-device-tree-support.patch b/target/linux/ipq806x/patches-4.1/030-hwspinlock-core-add-device-tree-support.patch new file mode 100644 index 0000000..04f35b7 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/030-hwspinlock-core-add-device-tree-support.patch @@ -0,0 +1,167 @@ +From fb7737e949e31d8a71acee6bbb670f32dbd2a2c0 Mon Sep 17 00:00:00 2001 +From: Suman Anna +Date: Wed, 4 Mar 2015 20:01:14 -0600 +Subject: [PATCH] hwspinlock/core: add device tree support + +This patch adds a new OF-friendly API of_hwspin_lock_get_id() +for hwspinlock clients to use/request locks from a hwspinlock +device instantiated through a device-tree blob. This new API +can be used by hwspinlock clients to get the id for a specific +lock using the phandle + args specifier, so that it can be +requested using the available hwspin_lock_request_specific() +API. + +Signed-off-by: Suman Anna +Reviewed-by: Bjorn Andersson +[small comment clarification] +Signed-off-by: Ohad Ben-Cohen +--- + Documentation/hwspinlock.txt | 10 +++++ + drivers/hwspinlock/hwspinlock_core.c | 79 ++++++++++++++++++++++++++++++++++++ + include/linux/hwspinlock.h | 7 ++++ + 3 files changed, 96 insertions(+) + +--- a/Documentation/hwspinlock.txt ++++ b/Documentation/hwspinlock.txt +@@ -48,6 +48,16 @@ independent, drivers. + ids for predefined purposes. + Should be called from a process context (might sleep). + ++ int of_hwspin_lock_get_id(struct device_node *np, int index); ++ - retrieve the global lock id for an OF phandle-based specific lock. ++ This function provides a means for DT users of a hwspinlock module ++ to get the global lock id of a specific hwspinlock, so that it can ++ be requested using the normal hwspin_lock_request_specific() API. ++ The function returns a lock id number on success, -EPROBE_DEFER if ++ the hwspinlock device is not yet registered with the core, or other ++ error values. ++ Should be called from a process context (might sleep). ++ + int hwspin_lock_free(struct hwspinlock *hwlock); + - free a previously-assigned hwspinlock; returns 0 on success, or an + appropriate error code on failure (e.g. -EINVAL if the hwspinlock +--- a/drivers/hwspinlock/hwspinlock_core.c ++++ b/drivers/hwspinlock/hwspinlock_core.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + + #include "hwspinlock_internal.h" + +@@ -257,6 +258,84 @@ void __hwspin_unlock(struct hwspinlock * + } + EXPORT_SYMBOL_GPL(__hwspin_unlock); + ++/** ++ * of_hwspin_lock_simple_xlate - translate hwlock_spec to return a lock id ++ * @bank: the hwspinlock device bank ++ * @hwlock_spec: hwlock specifier as found in the device tree ++ * ++ * This is a simple translation function, suitable for hwspinlock platform ++ * drivers that only has a lock specifier length of 1. ++ * ++ * Returns a relative index of the lock within a specified bank on success, ++ * or -EINVAL on invalid specifier cell count. ++ */ ++static inline int ++of_hwspin_lock_simple_xlate(const struct of_phandle_args *hwlock_spec) ++{ ++ if (WARN_ON(hwlock_spec->args_count != 1)) ++ return -EINVAL; ++ ++ return hwlock_spec->args[0]; ++} ++ ++/** ++ * of_hwspin_lock_get_id() - get lock id for an OF phandle-based specific lock ++ * @np: device node from which to request the specific hwlock ++ * @index: index of the hwlock in the list of values ++ * ++ * This function provides a means for DT users of the hwspinlock module to ++ * get the global lock id of a specific hwspinlock using the phandle of the ++ * hwspinlock device, so that it can be requested using the normal ++ * hwspin_lock_request_specific() API. ++ * ++ * Returns the global lock id number on success, -EPROBE_DEFER if the hwspinlock ++ * device is not yet registered, -EINVAL on invalid args specifier value or an ++ * appropriate error as returned from the OF parsing of the DT client node. ++ */ ++int of_hwspin_lock_get_id(struct device_node *np, int index) ++{ ++ struct of_phandle_args args; ++ struct hwspinlock *hwlock; ++ struct radix_tree_iter iter; ++ void **slot; ++ int id; ++ int ret; ++ ++ ret = of_parse_phandle_with_args(np, "hwlocks", "#hwlock-cells", index, ++ &args); ++ if (ret) ++ return ret; ++ ++ /* Find the hwspinlock device: we need its base_id */ ++ ret = -EPROBE_DEFER; ++ rcu_read_lock(); ++ radix_tree_for_each_slot(slot, &hwspinlock_tree, &iter, 0) { ++ hwlock = radix_tree_deref_slot(slot); ++ if (unlikely(!hwlock)) ++ continue; ++ ++ if (hwlock->bank->dev->of_node == args.np) { ++ ret = 0; ++ break; ++ } ++ } ++ rcu_read_unlock(); ++ if (ret < 0) ++ goto out; ++ ++ id = of_hwspin_lock_simple_xlate(&args); ++ if (id < 0 || id >= hwlock->bank->num_locks) { ++ ret = -EINVAL; ++ goto out; ++ } ++ id += hwlock->bank->base_id; ++ ++out: ++ of_node_put(args.np); ++ return ret ? ret : id; ++} ++EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id); ++ + static int hwspin_lock_register_single(struct hwspinlock *hwlock, int id) + { + struct hwspinlock *tmp; +--- a/include/linux/hwspinlock.h ++++ b/include/linux/hwspinlock.h +@@ -26,6 +26,7 @@ + #define HWLOCK_IRQ 0x02 /* Disable interrupts, don't save state */ + + struct device; ++struct device_node; + struct hwspinlock; + struct hwspinlock_device; + struct hwspinlock_ops; +@@ -66,6 +67,7 @@ int hwspin_lock_unregister(struct hwspin + struct hwspinlock *hwspin_lock_request(void); + struct hwspinlock *hwspin_lock_request_specific(unsigned int id); + int hwspin_lock_free(struct hwspinlock *hwlock); ++int of_hwspin_lock_get_id(struct device_node *np, int index); + int hwspin_lock_get_id(struct hwspinlock *hwlock); + int __hwspin_lock_timeout(struct hwspinlock *, unsigned int, int, + unsigned long *); +@@ -120,6 +122,11 @@ void __hwspin_unlock(struct hwspinlock * + { + } + ++static inline int of_hwspin_lock_get_id(struct device_node *np, int index) ++{ ++ return 0; ++} ++ + static inline int hwspin_lock_get_id(struct hwspinlock *hwlock) + { + return 0; diff --git a/target/linux/ipq806x/patches-4.1/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch b/target/linux/ipq806x/patches-4.1/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch new file mode 100644 index 0000000..581b199 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/031-hwspinlock-qcom-Add-support-for-Qualcomm-HW-Mutex-bl.patch @@ -0,0 +1,234 @@ +From 19a0f61224d2d91860fa8291ab63cb104ee86bdd Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Tue, 24 Mar 2015 10:11:05 -0700 +Subject: [PATCH] hwspinlock: qcom: Add support for Qualcomm HW Mutex block + +Add driver for Qualcomm Hardware Mutex block found in many Qualcomm +SoCs. + +Based on initial effort by Kumar Gala + +Signed-off-by: Bjorn Andersson +Reviewed-by: Andy Gross +Reviewed-by: Jeffrey Hugo +Signed-off-by: Ohad Ben-Cohen +--- + drivers/hwspinlock/Kconfig | 12 +++ + drivers/hwspinlock/Makefile | 1 + + drivers/hwspinlock/qcom_hwspinlock.c | 181 +++++++++++++++++++++++++++++++++++ + 3 files changed, 194 insertions(+) + create mode 100644 drivers/hwspinlock/qcom_hwspinlock.c + +--- a/drivers/hwspinlock/Kconfig ++++ b/drivers/hwspinlock/Kconfig +@@ -18,6 +18,18 @@ config HWSPINLOCK_OMAP + + If unsure, say N. + ++config HWSPINLOCK_QCOM ++ tristate "Qualcomm Hardware Spinlock device" ++ depends on ARCH_QCOM ++ select HWSPINLOCK ++ select MFD_SYSCON ++ help ++ Say y here to support the Qualcomm Hardware Mutex functionality, which ++ provides a synchronisation mechanism for the various processors on ++ the SoC. ++ ++ If unsure, say N. ++ + config HSEM_U8500 + tristate "STE Hardware Semaphore functionality" + depends on ARCH_U8500 +--- a/drivers/hwspinlock/Makefile ++++ b/drivers/hwspinlock/Makefile +@@ -4,4 +4,5 @@ + + obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o + obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o ++obj-$(CONFIG_HWSPINLOCK_QCOM) += qcom_hwspinlock.o + obj-$(CONFIG_HSEM_U8500) += u8500_hsem.o +--- /dev/null ++++ b/drivers/hwspinlock/qcom_hwspinlock.c +@@ -0,0 +1,181 @@ ++/* ++ * Copyright (c) 2013, The Linux Foundation. All rights reserved. ++ * Copyright (c) 2015, Sony Mobile Communications AB ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "hwspinlock_internal.h" ++ ++#define QCOM_MUTEX_APPS_PROC_ID 1 ++#define QCOM_MUTEX_NUM_LOCKS 32 ++ ++static int qcom_hwspinlock_trylock(struct hwspinlock *lock) ++{ ++ struct regmap_field *field = lock->priv; ++ u32 lock_owner; ++ int ret; ++ ++ ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID); ++ if (ret) ++ return ret; ++ ++ ret = regmap_field_read(field, &lock_owner); ++ if (ret) ++ return ret; ++ ++ return lock_owner == QCOM_MUTEX_APPS_PROC_ID; ++} ++ ++static void qcom_hwspinlock_unlock(struct hwspinlock *lock) ++{ ++ struct regmap_field *field = lock->priv; ++ u32 lock_owner; ++ int ret; ++ ++ ret = regmap_field_read(field, &lock_owner); ++ if (ret) { ++ pr_err("%s: unable to query spinlock owner\n", __func__); ++ return; ++ } ++ ++ if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) { ++ pr_err("%s: spinlock not owned by us (actual owner is %d)\n", ++ __func__, lock_owner); ++ } ++ ++ ret = regmap_field_write(field, 0); ++ if (ret) ++ pr_err("%s: failed to unlock spinlock\n", __func__); ++} ++ ++static const struct hwspinlock_ops qcom_hwspinlock_ops = { ++ .trylock = qcom_hwspinlock_trylock, ++ .unlock = qcom_hwspinlock_unlock, ++}; ++ ++static const struct of_device_id qcom_hwspinlock_of_match[] = { ++ { .compatible = "qcom,sfpb-mutex" }, ++ { .compatible = "qcom,tcsr-mutex" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); ++ ++static int qcom_hwspinlock_probe(struct platform_device *pdev) ++{ ++ struct hwspinlock_device *bank; ++ struct device_node *syscon; ++ struct reg_field field; ++ struct regmap *regmap; ++ size_t array_size; ++ u32 stride; ++ u32 base; ++ int ret; ++ int i; ++ ++ syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0); ++ if (!syscon) { ++ dev_err(&pdev->dev, "no syscon property\n"); ++ return -ENODEV; ++ } ++ ++ regmap = syscon_node_to_regmap(syscon); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, &base); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "no offset in syscon\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, &stride); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "no stride syscon\n"); ++ return -EINVAL; ++ } ++ ++ array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); ++ bank = devm_kzalloc(&pdev->dev, sizeof(*bank) + array_size, GFP_KERNEL); ++ if (!bank) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, bank); ++ ++ for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { ++ field.reg = base + i * stride; ++ field.lsb = 0; ++ field.msb = 32; ++ ++ bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, ++ regmap, field); ++ } ++ ++ pm_runtime_enable(&pdev->dev); ++ ++ ret = hwspin_lock_register(bank, &pdev->dev, &qcom_hwspinlock_ops, ++ 0, QCOM_MUTEX_NUM_LOCKS); ++ if (ret) ++ pm_runtime_disable(&pdev->dev); ++ ++ return ret; ++} ++ ++static int qcom_hwspinlock_remove(struct platform_device *pdev) ++{ ++ struct hwspinlock_device *bank = platform_get_drvdata(pdev); ++ int ret; ++ ++ ret = hwspin_lock_unregister(bank); ++ if (ret) { ++ dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); ++ return ret; ++ } ++ ++ pm_runtime_disable(&pdev->dev); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_hwspinlock_driver = { ++ .probe = qcom_hwspinlock_probe, ++ .remove = qcom_hwspinlock_remove, ++ .driver = { ++ .name = "qcom_hwspinlock", ++ .of_match_table = qcom_hwspinlock_of_match, ++ }, ++}; ++ ++static int __init qcom_hwspinlock_init(void) ++{ ++ return platform_driver_register(&qcom_hwspinlock_driver); ++} ++/* board init code might need to reserve hwspinlocks for predefined purposes */ ++postcore_initcall(qcom_hwspinlock_init); ++ ++static void __exit qcom_hwspinlock_exit(void) ++{ ++ platform_driver_unregister(&qcom_hwspinlock_driver); ++} ++module_exit(qcom_hwspinlock_exit); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs"); diff --git a/target/linux/ipq806x/patches-4.1/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch b/target/linux/ipq806x/patches-4.1/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch new file mode 100644 index 0000000..70d5a58 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/032-hwspinlock-qcom-Correct-msb-in-regmap_field.patch @@ -0,0 +1,26 @@ +From bd5717a4632cdecafe82d03de7dcb3b1876e2828 Mon Sep 17 00:00:00 2001 +From: Bjorn Andersson +Date: Fri, 26 Jun 2015 14:47:21 -0700 +Subject: [PATCH] hwspinlock: qcom: Correct msb in regmap_field + +msb of the regmap_field was mistakenly given the value 32, to set all bits +in the regmap update mask; although incorrect this worked until 921cc294, +where the mask calculation was corrected. + +Signed-off-by: Bjorn Andersson +Signed-off-by: Ohad Ben-Cohen +--- + drivers/hwspinlock/qcom_hwspinlock.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/drivers/hwspinlock/qcom_hwspinlock.c ++++ b/drivers/hwspinlock/qcom_hwspinlock.c +@@ -123,7 +123,7 @@ static int qcom_hwspinlock_probe(struct + for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { + field.reg = base + i * stride; + field.lsb = 0; +- field.msb = 32; ++ field.msb = 31; + + bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, + regmap, field); diff --git a/target/linux/ipq806x/patches-4.1/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-4.1/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch new file mode 100644 index 0000000..a126d69 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/033-ARM-qcom-add-SFPB-nodes-to-IPQ806x-dts.patch @@ -0,0 +1,33 @@ +From c7c482da19a5e4ba7101198c21c2434056b0b2da Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 13 Aug 2015 09:45:26 -0700 +Subject: [PATCH 1/3] ARM: qcom: add SFPB nodes to IPQ806x dts + +Add one new node to the ipq806x.dtsi file to declare & register the +hardware spinlock devices. This mechanism is required to be used by +other drivers such as SMEM. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -329,5 +329,16 @@ + #reset-cells = <1>; + }; + ++ sfpb_mutex_block: syscon@1200600 { ++ compatible = "syscon"; ++ reg = <0x01200600 0x100>; ++ }; + }; ++ ++ sfpb_mutex: sfpb-mutex { ++ compatible = "qcom,sfpb-mutex"; ++ syscon = <&sfpb_mutex_block 4 4>; ++ ++ #hwlock-cells = <1>; ++ }; + }; diff --git a/target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch b/target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch new file mode 100644 index 0000000..d22db22 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/034-soc-qcom-Add-device-tree-binding-for-SMEM.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,1/2] soc: qcom: Add device tree binding for SMEM +From: Bjorn Andersson +X-Patchwork-Id: 6202201 +Message-Id: <1428795178-24312-1-git-send-email-bjorn.andersson@sonymobile.com> +To: Rob Herring , Pawel Moll , + Mark Rutland , + Ian Campbell , + Kumar Gala , Jeffrey Hugo , + Andry Gross +Cc: , + linux-arm-msm , + +Date: Sat, 11 Apr 2015 16:32:57 -0700 + +Add device tree binding documentation for the Qualcom Shared Memory +manager. + +Signed-off-by: Bjorn Andersson + +--- +Changes since v1: +- None + + .../devicetree/bindings/soc/qcom/qcom,smem.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/soc/qcom/qcom,smem.txt +@@ -0,0 +1,49 @@ ++Qualcomm Shared Memory binding ++ ++This binding describes the Qualcomm Shared Memory, used to share data between ++various subsystems and OSes in Qualcomm platforms. ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be: ++ "qcom,smem" ++ ++- memory-region: ++ Usage: required ++ Value type: ++ Definition: handle to memory reservation for main smem memory region. ++ ++- reg: ++ Usage: optional ++ Value type: ++ Definition: base address and size pair for any additional memory areas ++ of the shared memory. ++ ++- hwspinlocks: ++ Usage: required ++ Value type: ++ Definition: reference to a hwspinlock used to protect allocations from ++ the shared memory ++ ++= EXAMPLE ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ ++ smem_region: smem@fa00000 { ++ reg = <0xfa00000 0x200000>; ++ no-map; ++ }; ++ }; ++ ++ smem@fa00000 { ++ compatible = "qcom,smem"; ++ ++ memory-region = <&smem_region>; ++ reg = <0xfc428000 0x4000>; ++ ++ hwlocks = <&tcsr_mutex 3>; ++ }; diff --git a/target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch b/target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch new file mode 100644 index 0000000..5598da2 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/035-soc-qcom-Add-Shared-Memory-Manager-driver.patch @@ -0,0 +1,841 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,2/2] soc: qcom: Add Shared Memory Manager driver +From: Bjorn Andersson +X-Patchwork-Id: 6202211 +Message-Id: <1428795178-24312-2-git-send-email-bjorn.andersson@sonymobile.com> +To: Kumar Gala , Andy Gross , + David Brown , Jeffrey Hugo +Cc: , , + +Date: Sat, 11 Apr 2015 16:32:58 -0700 + +The Shared Memory Manager driver implements an interface for allocating +and accessing items in the memory area shared among all of the +processors in a Qualcomm platform. + +Signed-off-by: Bjorn Andersson +Reviewed-by: Andy Gross +Tested-by: Andy Gross + +--- +Changes since v1: +- ioremapping the regions nocache +- improved documentation of the two regions of partitions +- corrected free space check in private allocator + + drivers/soc/qcom/Kconfig | 7 + + drivers/soc/qcom/Makefile | 1 + + drivers/soc/qcom/smem.c | 768 ++++++++++++++++++++++++++++++++++++++++++ + include/linux/soc/qcom/smem.h | 14 + + 4 files changed, 790 insertions(+) + create mode 100644 drivers/soc/qcom/smem.c + create mode 100644 include/linux/soc/qcom/smem.h + +--- a/drivers/soc/qcom/Kconfig ++++ b/drivers/soc/qcom/Kconfig +@@ -10,3 +10,10 @@ config QCOM_GSBI + functions for connecting the underlying serial UART, SPI, and I2C + devices to the output pins. + ++config QCOM_SMEM ++ tristate "Qualcomm Shared Memory Manager (SMEM)" ++ depends on ARCH_QCOM ++ help ++ Say y here to enable support for the Qualcomm Shared Memory Manager. ++ The driver provides an interface to items in a heap shared among all ++ processors in a Qualcomm platform. +--- a/drivers/soc/qcom/Makefile ++++ b/drivers/soc/qcom/Makefile +@@ -1 +1,2 @@ + obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o ++obj-$(CONFIG_QCOM_SMEM) += smem.o +--- /dev/null ++++ b/drivers/soc/qcom/smem.c +@@ -0,0 +1,768 @@ ++/* ++ * Copyright (c) 2015, Sony Mobile Communications AB. ++ * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* ++ * The Qualcomm shared memory system is a allocate only heap structure that ++ * consists of one of more memory areas that can be accessed by the processors ++ * in the SoC. ++ * ++ * All systems contains a global heap, accessible by all processors in the SoC, ++ * with a table of contents data structure (@smem_header) at the beginning of ++ * the main shared memory block. ++ * ++ * The global header contains metadata for allocations as well as a fixed list ++ * of 512 entries (@smem_global_entry) that can be initialized to reference ++ * parts of the shared memory space. ++ * ++ * ++ * In addition to this global heap a set of "private" heaps can be set up at ++ * boot time with access restrictions so that only certain processor pairs can ++ * access the data. ++ * ++ * These partitions are referenced from an optional partition table ++ * (@smem_ptable), that is found 4kB from the end of the main smem region. The ++ * partition table entries (@smem_ptable_entry) lists the involved processors ++ * (or hosts) and their location in the main shared memory region. ++ * ++ * Each partition starts with a header (@smem_partition_header) that identifies ++ * the partition and holds properties for the two internal memory regions. The ++ * two regions are cached and non-cached memory respectively. Each region ++ * contain a link list of allocation headers (@smem_private_entry) followed by ++ * their data. ++ * ++ * Items in the non-cached region are allocated from the start of the partition ++ * while items in the cached region are allocated from the end. The free area ++ * is hence the region between the cached and non-cached offsets. ++ * ++ * ++ * To synchronize allocations in the shared memory heaps a remote spinlock must ++ * be held - currently lock number 3 of the sfpb or tcsr is used for this on all ++ * platforms. ++ * ++ */ ++ ++/** ++ * struct smem_proc_comm - proc_comm communication struct (legacy) ++ * @command: current command to be executed ++ * @status: status of the currently requested command ++ * @params: parameters to the command ++ */ ++struct smem_proc_comm { ++ u32 command; ++ u32 status; ++ u32 params[2]; ++}; ++ ++/** ++ * struct smem_global_entry - entry to reference smem items on the heap ++ * @allocated: boolean to indicate if this entry is used ++ * @offset: offset to the allocated space ++ * @size: size of the allocated space, 8 byte aligned ++ * @aux_base: base address for the memory region used by this unit, or 0 for ++ * the default region. bits 0,1 are reserved ++ */ ++struct smem_global_entry { ++ u32 allocated; ++ u32 offset; ++ u32 size; ++ u32 aux_base; /* bits 1:0 reserved */ ++}; ++#define AUX_BASE_MASK 0xfffffffc ++ ++/** ++ * struct smem_header - header found in beginning of primary smem region ++ * @proc_comm: proc_comm communication interface (legacy) ++ * @version: array of versions for the various subsystems ++ * @initialized: boolean to indicate that smem is initialized ++ * @free_offset: index of the first unallocated byte in smem ++ * @available: number of bytes available for allocation ++ * @reserved: reserved field, must be 0 ++ * toc: array of references to items ++ */ ++struct smem_header { ++ struct smem_proc_comm proc_comm[4]; ++ u32 version[32]; ++ u32 initialized; ++ u32 free_offset; ++ u32 available; ++ u32 reserved; ++ struct smem_global_entry toc[]; ++}; ++ ++/** ++ * struct smem_ptable_entry - one entry in the @smem_ptable list ++ * @offset: offset, within the main shared memory region, of the partition ++ * @size: size of the partition ++ * @flags: flags for the partition (currently unused) ++ * @host0: first processor/host with access to this partition ++ * @host1: second processor/host with access to this partition ++ * @reserved: reserved entries for later use ++ */ ++struct smem_ptable_entry { ++ u32 offset; ++ u32 size; ++ u32 flags; ++ u16 host0; ++ u16 host1; ++ u32 reserved[8]; ++}; ++ ++/** ++ * struct smem_ptable - partition table for the private partitions ++ * @magic: magic number, must be SMEM_PTABLE_MAGIC ++ * @version: version of the partition table ++ * @num_entries: number of partitions in the table ++ * @reserved: for now reserved entries ++ * @entry: list of @smem_ptable_entry for the @num_entries partitions ++ */ ++struct smem_ptable { ++ u32 magic; ++ u32 version; ++ u32 num_entries; ++ u32 reserved[5]; ++ struct smem_ptable_entry entry[]; ++}; ++#define SMEM_PTABLE_MAGIC 0x434f5424 /* "$TOC" */ ++ ++/** ++ * struct smem_partition_header - header of the partitions ++ * @magic: magic number, must be SMEM_PART_MAGIC ++ * @host0: first processor/host with access to this partition ++ * @host1: second processor/host with access to this partition ++ * @size: size of the partition ++ * @offset_free_uncached: offset to the first free byte of uncached memory in ++ * this partition ++ * @offset_free_cached: offset to the first free byte of cached memory in this ++ * partition ++ * @reserved: for now reserved entries ++ */ ++struct smem_partition_header { ++ u32 magic; ++ u16 host0; ++ u16 host1; ++ u32 size; ++ u32 offset_free_uncached; ++ u32 offset_free_cached; ++ u32 reserved[3]; ++}; ++#define SMEM_PART_MAGIC 0x54525024 /* "$PRT" */ ++ ++/** ++ * struct smem_private_entry - header of each item in the private partition ++ * @canary: magic number, must be SMEM_PRIVATE_CANARY ++ * @item: identifying number of the smem item ++ * @size: size of the data, including padding bytes ++ * @padding_data: number of bytes of padding of data ++ * @padding_hdr: number of bytes of padding between the header and the data ++ * @reserved: for now reserved entry ++ */ ++struct smem_private_entry { ++ u16 canary; ++ u16 item; ++ u32 size; /* includes padding bytes */ ++ u16 padding_data; ++ u16 padding_hdr; ++ u32 reserved; ++}; ++#define SMEM_PRIVATE_CANARY 0xa5a5 ++ ++/* ++ * Item 3 of the global heap contains an array of versions for the various ++ * software components in the SoC. We verify that the boot loader version is ++ * what the expected version (SMEM_EXPECTED_VERSION) as a sanity check. ++ */ ++#define SMEM_ITEM_VERSION 3 ++#define SMEM_MASTER_SBL_VERSION_INDEX 7 ++#define SMEM_EXPECTED_VERSION 11 ++ ++/* ++ * The first 8 items are only to be allocated by the boot loader while ++ * initializing the heap. ++ */ ++#define SMEM_ITEM_LAST_FIXED 8 ++ ++/* Highest accepted item number, for both global and private heaps */ ++#define SMEM_ITEM_LAST 512 ++ ++/* Processor/host identifier for the application processor */ ++#define SMEM_HOST_APPS 0 ++ ++/* Max number of processors/hosts in a system */ ++#define SMEM_HOST_COUNT 7 ++ ++/** ++ * struct smem_region - representation of a chunk of memory used for smem ++ * @aux_base: identifier of aux_mem base ++ * @virt_base: virtual base address of memory with this aux_mem identifier ++ * @size: size of the memory region ++ */ ++struct smem_region { ++ u32 aux_base; ++ void __iomem *virt_base; ++ size_t size; ++}; ++ ++/** ++ * struct qcom_smem - device data for the smem device ++ * @dev: device pointer ++ * @hwlock: reference to a hwspinlock ++ * @partitions: list of pointers to partitions affecting the current ++ * processor/host ++ * @num_regions: number of @regions ++ * @regions: list of the memory regions defining the shared memory ++ */ ++struct qcom_smem { ++ struct device *dev; ++ ++ struct hwspinlock *hwlock; ++ ++ struct smem_partition_header *partitions[SMEM_HOST_COUNT]; ++ ++ unsigned num_regions; ++ struct smem_region regions[0]; ++}; ++ ++/* Pointer to the one and only smem handle */ ++static struct qcom_smem *__smem; ++ ++/* Timeout (ms) for the trylock of remote spinlocks */ ++#define HWSPINLOCK_TIMEOUT 1000 ++ ++static int qcom_smem_alloc_private(struct qcom_smem *smem, ++ unsigned host, ++ unsigned item, ++ size_t size) ++{ ++ struct smem_partition_header *phdr; ++ struct smem_private_entry *hdr; ++ size_t alloc_size; ++ void *p; ++ ++ /* We're not going to find it if there's no matching partition */ ++ if (host >= SMEM_HOST_COUNT || !smem->partitions[host]) ++ return -ENOENT; ++ ++ phdr = smem->partitions[host]; ++ ++ p = (void *)phdr + sizeof(*phdr); ++ while (p < (void *)phdr + phdr->offset_free_uncached) { ++ hdr = p; ++ ++ if (hdr->canary != SMEM_PRIVATE_CANARY) { ++ dev_err(smem->dev, ++ "Found invalid canary in host %d partition\n", ++ host); ++ return -EINVAL; ++ } ++ ++ if (hdr->item == item) ++ return -EEXIST; ++ ++ p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; ++ } ++ ++ /* Check that we don't grow into the cached region */ ++ alloc_size = sizeof(*hdr) + ALIGN(size, 8); ++ if (p + alloc_size >= (void *)phdr + phdr->offset_free_cached) { ++ dev_err(smem->dev, "Out of memory\n"); ++ return -ENOSPC; ++ } ++ ++ hdr = p; ++ hdr->canary = SMEM_PRIVATE_CANARY; ++ hdr->item = item; ++ hdr->size = ALIGN(size, 8); ++ hdr->padding_data = hdr->size - size; ++ hdr->padding_hdr = 0; ++ ++ /* ++ * Ensure the header is written before we advance the free offset, so ++ * that remote processors that does not take the remote spinlock still ++ * gets a consistent view of the linked list. ++ */ ++ wmb(); ++ phdr->offset_free_uncached += alloc_size; ++ ++ return 0; ++} ++ ++static int qcom_smem_alloc_global(struct qcom_smem *smem, ++ unsigned item, ++ size_t size) ++{ ++ struct smem_header *header; ++ struct smem_global_entry *entry; ++ ++ if (WARN_ON(item >= SMEM_ITEM_LAST)) ++ return -EINVAL; ++ ++ header = smem->regions[0].virt_base; ++ entry = &header->toc[item]; ++ if (entry->allocated) ++ return -EEXIST; ++ ++ size = ALIGN(size, 8); ++ if (WARN_ON(size > header->available)) ++ return -ENOMEM; ++ ++ entry->offset = header->free_offset; ++ entry->size = size; ++ ++ /* ++ * Ensure the header is consistent before we mark the item allocated, ++ * so that remote processors will get a consistent view of the item ++ * even though they do not take the spinlock on read. ++ */ ++ wmb(); ++ entry->allocated = 1; ++ ++ header->free_offset += size; ++ header->available -= size; ++ ++ return 0; ++} ++ ++/** ++ * qcom_smem_alloc - allocate space for a smem item ++ * @host: remote processor id, or -1 ++ * @item: smem item handle ++ * @size: number of bytes to be allocated ++ * ++ * Allocate space for a given smem item of size @size, given that the item is ++ * not yet allocated. ++ */ ++int qcom_smem_alloc(unsigned host, unsigned item, size_t size) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!__smem) ++ return -EPROBE_DEFER; ++ ++ if (item < SMEM_ITEM_LAST_FIXED) { ++ dev_err(__smem->dev, ++ "Rejecting allocation of static entry %d\n", item); ++ return -EINVAL; ++ } ++ ++ ret = hwspin_lock_timeout_irqsave(__smem->hwlock, ++ HWSPINLOCK_TIMEOUT, ++ &flags); ++ if (ret) ++ return ret; ++ ++ ret = qcom_smem_alloc_private(__smem, host, item, size); ++ if (ret == -ENOENT) ++ ret = qcom_smem_alloc_global(__smem, item, size); ++ ++ hwspin_unlock_irqrestore(__smem->hwlock, &flags); ++ ++ return ret; ++} ++EXPORT_SYMBOL(qcom_smem_alloc); ++ ++static int qcom_smem_get_global(struct qcom_smem *smem, ++ unsigned item, ++ void **ptr, ++ size_t *size) ++{ ++ struct smem_header *header; ++ struct smem_region *area; ++ struct smem_global_entry *entry; ++ u32 aux_base; ++ unsigned i; ++ ++ if (WARN_ON(item >= SMEM_ITEM_LAST)) ++ return -EINVAL; ++ ++ header = smem->regions[0].virt_base; ++ entry = &header->toc[item]; ++ if (!entry->allocated) ++ return -ENXIO; ++ ++ if (ptr != NULL) { ++ aux_base = entry->aux_base & AUX_BASE_MASK; ++ ++ for (i = 0; i < smem->num_regions; i++) { ++ area = &smem->regions[i]; ++ ++ if (area->aux_base == aux_base || !aux_base) { ++ *ptr = area->virt_base + entry->offset; ++ break; ++ } ++ } ++ } ++ if (size != NULL) ++ *size = entry->size; ++ ++ return 0; ++} ++ ++static int qcom_smem_get_private(struct qcom_smem *smem, ++ unsigned host, ++ unsigned item, ++ void **ptr, ++ size_t *size) ++{ ++ struct smem_partition_header *phdr; ++ struct smem_private_entry *hdr; ++ void *p; ++ ++ /* We're not going to find it if there's no matching partition */ ++ if (host >= SMEM_HOST_COUNT || !smem->partitions[host]) ++ return -ENOENT; ++ ++ phdr = smem->partitions[host]; ++ ++ p = (void *)phdr + sizeof(*phdr); ++ while (p < (void *)phdr + phdr->offset_free_uncached) { ++ hdr = p; ++ ++ if (hdr->canary != SMEM_PRIVATE_CANARY) { ++ dev_err(smem->dev, ++ "Found invalid canary in host %d partition\n", ++ host); ++ return -EINVAL; ++ } ++ ++ if (hdr->item == item) { ++ if (ptr != NULL) ++ *ptr = p + sizeof(*hdr) + hdr->padding_hdr; ++ ++ if (size != NULL) ++ *size = hdr->size - hdr->padding_data; ++ ++ return 0; ++ } ++ ++ p += sizeof(*hdr) + hdr->padding_hdr + hdr->size; ++ } ++ ++ return -ENOENT; ++} ++ ++/** ++ * qcom_smem_get - resolve ptr of size of a smem item ++ * @host: the remote processor, or -1 ++ * @item: smem item handle ++ * @ptr: pointer to be filled out with address of the item ++ * @size: pointer to be filled out with size of the item ++ * ++ * Looks up pointer and size of a smem item. ++ */ ++int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size) ++{ ++ unsigned long flags; ++ int ret; ++ ++ if (!__smem) ++ return -EPROBE_DEFER; ++ ++ ret = hwspin_lock_timeout_irqsave(__smem->hwlock, ++ HWSPINLOCK_TIMEOUT, ++ &flags); ++ if (ret) ++ return ret; ++ ++ ret = qcom_smem_get_private(__smem, host, item, ptr, size); ++ if (ret == -ENOENT) ++ ret = qcom_smem_get_global(__smem, item, ptr, size); ++ ++ hwspin_unlock_irqrestore(__smem->hwlock, &flags); ++ return ret; ++ ++} ++EXPORT_SYMBOL(qcom_smem_get); ++ ++/** ++ * qcom_smem_get_free_space - retrieve amont of free space in a partition ++ * @host: the remote processor identifing a partition, or -1 ++ * ++ * To be used by smem clients as a quick way to determine if any new ++ * allocations has been made. ++ */ ++int qcom_smem_get_free_space(unsigned host) ++{ ++ struct smem_partition_header *phdr; ++ struct smem_header *header; ++ unsigned ret; ++ ++ if (!__smem) ++ return -EPROBE_DEFER; ++ ++ if (host < SMEM_HOST_COUNT && __smem->partitions[host]) { ++ phdr = __smem->partitions[host]; ++ ret = phdr->offset_free_uncached; ++ } else { ++ header = __smem->regions[0].virt_base; ++ ret = header->available; ++ } ++ ++ return ret; ++} ++EXPORT_SYMBOL(qcom_smem_get_free_space); ++ ++static int qcom_smem_get_sbl_version(struct qcom_smem *smem) ++{ ++ unsigned *versions; ++ size_t size; ++ int ret; ++ ++ ret = qcom_smem_get_global(smem, SMEM_ITEM_VERSION, ++ (void **)&versions, &size); ++ if (ret < 0) { ++ dev_err(smem->dev, "Unable to read the version item\n"); ++ return -ENOENT; ++ } ++ ++ if (size < sizeof(unsigned) * SMEM_MASTER_SBL_VERSION_INDEX) { ++ dev_err(smem->dev, "Version item is too small\n"); ++ return -EINVAL; ++ } ++ ++ return versions[SMEM_MASTER_SBL_VERSION_INDEX]; ++} ++ ++static int qcom_smem_enumerate_partitions(struct qcom_smem *smem, ++ unsigned local_host) ++{ ++ struct smem_partition_header *header; ++ struct smem_ptable_entry *entry; ++ struct smem_ptable *ptable; ++ unsigned remote_host; ++ int i; ++ ++ ptable = smem->regions[0].virt_base + smem->regions[0].size - 4 * 1024; ++ if (ptable->magic != SMEM_PTABLE_MAGIC) ++ return 0; ++ ++ if (ptable->version != 1) { ++ dev_err(smem->dev, ++ "Unsupported partition header version %d\n", ++ ptable->version); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < ptable->num_entries; i++) { ++ entry = &ptable->entry[i]; ++ ++ if (entry->host0 != local_host && entry->host1 != local_host) ++ continue; ++ ++ if (!entry->offset) ++ continue; ++ ++ if (!entry->size) ++ continue; ++ ++ if (entry->host0 == local_host) ++ remote_host = entry->host1; ++ else ++ remote_host = entry->host0; ++ ++ if (smem->partitions[remote_host]) { ++ dev_err(smem->dev, ++ "Already found a partition for host %d\n", ++ remote_host); ++ return -EINVAL; ++ } ++ ++ header = smem->regions[0].virt_base + entry->offset; ++ ++ if (header->magic != SMEM_PART_MAGIC) { ++ dev_err(smem->dev, ++ "Partition %d has invalid magic\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->host0 != local_host && header->host1 != local_host) { ++ dev_err(smem->dev, ++ "Partition %d hosts are invalid\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->host0 != remote_host && header->host1 != remote_host) { ++ dev_err(smem->dev, ++ "Partition %d hosts are invalid\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->size != entry->size) { ++ dev_err(smem->dev, ++ "Partition %d has invalid size\n", i); ++ return -EINVAL; ++ } ++ ++ if (header->offset_free_uncached > header->size) { ++ dev_err(smem->dev, ++ "Partition %d has invalid free pointer\n", i); ++ return -EINVAL; ++ } ++ ++ smem->partitions[remote_host] = header; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_count_mem_regions(struct platform_device *pdev) ++{ ++ struct resource *res; ++ int num_regions = 0; ++ int i; ++ ++ for (i = 0; i < pdev->num_resources; i++) { ++ res = &pdev->resource[i]; ++ ++ if (resource_type(res) == IORESOURCE_MEM) ++ num_regions++; ++ } ++ ++ return num_regions; ++} ++ ++static int qcom_smem_probe(struct platform_device *pdev) ++{ ++ struct smem_header *header; ++ struct device_node *np; ++ struct qcom_smem *smem; ++ struct resource *res; ++ struct resource r; ++ size_t array_size; ++ int num_regions = 0; ++ int hwlock_id; ++ u32 version; ++ int ret; ++ int i; ++ ++ num_regions = qcom_smem_count_mem_regions(pdev) + 1; ++ ++ array_size = num_regions * sizeof(struct smem_region); ++ smem = devm_kzalloc(&pdev->dev, sizeof(*smem) + array_size, GFP_KERNEL); ++ if (!smem) ++ return -ENOMEM; ++ ++ smem->dev = &pdev->dev; ++ smem->num_regions = num_regions; ++ ++ np = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); ++ if (!np) { ++ dev_err(&pdev->dev, "No memory-region specified\n"); ++ return -EINVAL; ++ } ++ ++ ret = of_address_to_resource(np, 0, &r); ++ of_node_put(np); ++ if (ret) ++ return ret; ++ ++ smem->regions[0].aux_base = (u32)r.start; ++ smem->regions[0].size = resource_size(&r); ++ smem->regions[0].virt_base = devm_ioremap_nocache(&pdev->dev, ++ r.start, ++ resource_size(&r)); ++ if (!smem->regions[0].virt_base) ++ return -ENOMEM; ++ ++ for (i = 1; i < num_regions; i++) { ++ res = platform_get_resource(pdev, IORESOURCE_MEM, i - 1); ++ ++ smem->regions[i].aux_base = (u32)res->start; ++ smem->regions[i].size = resource_size(res); ++ smem->regions[i].virt_base = devm_ioremap_nocache(&pdev->dev, ++ res->start, ++ resource_size(res)); ++ if (!smem->regions[i].virt_base) ++ return -ENOMEM; ++ } ++ ++ header = smem->regions[0].virt_base; ++ if (header->initialized != 1 || header->reserved) { ++ dev_err(&pdev->dev, "SMEM is not initilized by SBL\n"); ++ return -EINVAL; ++ } ++ ++ version = qcom_smem_get_sbl_version(smem); ++ if (version >> 16 != SMEM_EXPECTED_VERSION) { ++ dev_err(&pdev->dev, "Unsupported smem version 0x%x\n", version); ++ return -EINVAL; ++ } ++ ++ ret = qcom_smem_enumerate_partitions(smem, SMEM_HOST_APPS); ++ if (ret < 0) ++ return ret; ++ ++ hwlock_id = of_hwspin_lock_get_id(pdev->dev.of_node, 0); ++ if (hwlock_id < 0) { ++ dev_err(&pdev->dev, "failed to retrieve hwlock\n"); ++ return hwlock_id; ++ } ++ ++ smem->hwlock = hwspin_lock_request_specific(hwlock_id); ++ if (!smem->hwlock) ++ return -ENXIO; ++ ++ __smem = smem; ++ ++ return 0; ++} ++ ++static int qcom_smem_remove(struct platform_device *pdev) ++{ ++ hwspin_lock_free(__smem->hwlock); ++ __smem = NULL; ++ ++ return 0; ++} ++ ++static const struct of_device_id qcom_smem_of_match[] = { ++ { .compatible = "qcom,smem" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, qcom_smem_of_match); ++ ++static struct platform_driver qcom_smem_driver = { ++ .probe = qcom_smem_probe, ++ .remove = qcom_smem_remove, ++ .driver = { ++ .name = "qcom_smem", ++ .of_match_table = qcom_smem_of_match, ++ .suppress_bind_attrs = true, ++ }, ++}; ++ ++static int __init qcom_smem_init(void) ++{ ++ return platform_driver_register(&qcom_smem_driver); ++} ++arch_initcall(qcom_smem_init); ++ ++static void __exit qcom_smem_exit(void) ++{ ++ platform_driver_unregister(&qcom_smem_driver); ++} ++module_exit(qcom_smem_exit) ++ ++MODULE_AUTHOR("Bjorn Andersson "); ++MODULE_DESCRIPTION("Qualcomm Shared Memory Manager"); ++MODULE_LICENSE("GPLv2"); +--- /dev/null ++++ b/include/linux/soc/qcom/smem.h +@@ -0,0 +1,14 @@ ++#ifndef __QCOM_SMEM_H__ ++#define __QCOM_SMEM_H__ ++ ++struct device_node; ++struct qcom_smem; ++ ++#define QCOM_SMEM_HOST_ANY -1 ++ ++int qcom_smem_alloc(unsigned host, unsigned item, size_t size); ++int qcom_smem_get(unsigned host, unsigned item, void **ptr, size_t *size); ++ ++int qcom_smem_get_free_space(unsigned host); ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch b/target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch new file mode 100644 index 0000000..b55f174 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/036-ARM-qcom-add-SMEM-device-node-to-IPQ806x-dts.patch @@ -0,0 +1,36 @@ +From f212be3a6134db8dd7c5f6f0987536a669401fae Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 14 Aug 2015 11:17:20 -0700 +Subject: [PATCH 2/3] ARM: qcom: add SMEM device node to IPQ806x dts + +SMEM is used on IPQ806x to store various board related information such +as boot device and flash partition layout. We'll declare it as a device +so we can make use of it thanks to the new SMEM soc driver. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064.dtsi | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -55,7 +55,7 @@ + no-map; + }; + +- smem@41000000 { ++ smem: smem@41000000 { + reg = <0x41000000 0x200000>; + no-map; + }; +@@ -341,4 +341,10 @@ + + #hwlock-cells = <1>; + }; ++ ++ smem { ++ compatible = "qcom,smem"; ++ memory-region = <&smem>; ++ hwlocks = <&sfpb_mutex 3>; ++ }; + }; diff --git a/target/linux/ipq806x/patches-4.1/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch b/target/linux/ipq806x/patches-4.1/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch new file mode 100644 index 0000000..b2c8cd5 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/037-mtd-add-SMEM-parser-for-QCOM-platforms.patch @@ -0,0 +1,277 @@ +From 0501f76b138cf1dc11a313bb7a094da524b79337 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Thu, 13 Aug 2015 09:53:14 -0700 +Subject: [PATCH 3/3] mtd: add SMEM parser for QCOM platforms + +On QCOM platforms using MTD devices storage (such as IPQ806x), SMEM is +used to store partition layout. This new parser can now be used to read +SMEM and use it to register an MTD layout according to its content. + +Signed-off-by: Mathieu Olivari +--- + drivers/mtd/Kconfig | 7 ++ + drivers/mtd/Makefile | 1 + + drivers/mtd/qcom_smem_part.c | 231 +++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 239 insertions(+) + create mode 100644 drivers/mtd/qcom_smem_part.c + +--- a/drivers/mtd/Kconfig ++++ b/drivers/mtd/Kconfig +@@ -200,6 +200,13 @@ config MTD_MYLOADER_PARTS + You will still need the parsing functions to be called by the driver + for your particular device. It won't happen automatically. + ++config MTD_QCOM_SMEM_PARTS ++ tristate "QCOM SMEM partitioning support" ++ depends on QCOM_SMEM ++ help ++ This provides partitions parser for QCOM devices using SMEM ++ such as IPQ806x. ++ + comment "User Modules And Translation Layers" + + # +--- /dev/null ++++ b/drivers/mtd/qcom_smem_part.c +@@ -0,0 +1,231 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++/* Processor/host identifier for the application processor */ ++#define SMEM_HOST_APPS 0 ++ ++/* SMEM items index */ ++#define SMEM_AARM_PARTITION_TABLE 9 ++#define SMEM_BOOT_FLASH_TYPE 421 ++#define SMEM_BOOT_FLASH_BLOCK_SIZE 424 ++ ++/* SMEM Flash types */ ++#define SMEM_FLASH_NAND 2 ++#define SMEM_FLASH_SPI 6 ++ ++#define SMEM_PART_NAME_SZ 16 ++#define SMEM_PARTS_MAX 32 ++ ++struct smem_partition { ++ char name[SMEM_PART_NAME_SZ]; ++ __le32 start; ++ __le32 size; ++ __le32 attr; ++}; ++ ++struct smem_partition_table { ++ u8 magic[8]; ++ __le32 version; ++ __le32 len; ++ struct smem_partition parts[SMEM_PARTS_MAX]; ++}; ++ ++/* SMEM Magic values in partition table */ ++static const u8 SMEM_PTABLE_MAGIC[] = { ++ 0xaa, 0x73, 0xee, 0x55, ++ 0xdb, 0xbd, 0x5e, 0xe3, ++}; ++ ++static int qcom_smem_get_flash_blksz(u64 **smem_blksz) ++{ ++ int ret; ++ size_t size; ++ ++ ret = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_BLOCK_SIZE, ++ (void **) smem_blksz, &size); ++ ++ if (ret < 0) { ++ pr_err("Unable to read flash blksz from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ if (size != sizeof(**smem_blksz)) { ++ pr_err("Invalid flash blksz size in SMEM\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_get_flash_type(u64 **smem_flash_type) ++{ ++ int ret; ++ size_t size; ++ ++ ret = qcom_smem_get(SMEM_HOST_APPS, SMEM_BOOT_FLASH_TYPE, ++ (void **) smem_flash_type, &size); ++ ++ if (ret < 0) { ++ pr_err("Unable to read flash type from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ if (size != sizeof(**smem_flash_type)) { ++ pr_err("Invalid flash type size in SMEM\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int qcom_smem_get_flash_partitions(struct smem_partition_table **pparts) ++{ ++ int ret; ++ size_t size; ++ ++ ret = qcom_smem_get(SMEM_HOST_APPS, SMEM_AARM_PARTITION_TABLE, ++ (void **) pparts, &size); ++ ++ if (ret < 0) { ++ pr_err("Unable to read partition table from SMEM\n"); ++ return -ENOENT; ++ } ++ ++ return 0; ++} ++ ++static int of_dev_node_match(struct device *dev, void *data) ++{ ++ return dev->of_node == data; ++} ++ ++static bool is_spi_device(struct device_node *np) ++{ ++ struct device *dev; ++ ++ dev = bus_find_device(&spi_bus_type, NULL, np, of_dev_node_match); ++ if (!dev) ++ return false; ++ ++ put_device(dev); ++ return true; ++} ++ ++static int parse_qcom_smem_partitions(struct mtd_info *master, ++ struct mtd_partition **pparts, ++ struct mtd_part_parser_data *data) ++{ ++ struct smem_partition_table *smem_parts; ++ u64 *smem_flash_type, *smem_blksz; ++ struct mtd_partition *mtd_parts; ++ struct device_node *of_node = data->of_node; ++ int i, ret; ++ ++ /* ++ * SMEM will only store the partition table of the boot device. ++ * If this is not the boot device, do not return any partition. ++ */ ++ ret = qcom_smem_get_flash_type(&smem_flash_type); ++ if (ret < 0) ++ return ret; ++ ++ if ((*smem_flash_type == SMEM_FLASH_NAND && !mtd_type_is_nand(master)) ++ || (*smem_flash_type == SMEM_FLASH_SPI && !is_spi_device(of_node))) ++ return 0; ++ ++ /* ++ * Just for sanity purpose, make sure the block size in SMEM matches the ++ * block size of the MTD device ++ */ ++ ret = qcom_smem_get_flash_blksz(&smem_blksz); ++ if (ret < 0) ++ return ret; ++ ++ if (*smem_blksz != master->erasesize) { ++ pr_err("SMEM block size differs from MTD block size\n"); ++ return -EINVAL; ++ } ++ ++ /* Get partition pointer from SMEM */ ++ ret = qcom_smem_get_flash_partitions(&smem_parts); ++ if (ret < 0) ++ return ret; ++ ++ if (memcmp(SMEM_PTABLE_MAGIC, smem_parts->magic, ++ sizeof(SMEM_PTABLE_MAGIC))) { ++ pr_err("SMEM partition magic invalid\n"); ++ return -EINVAL; ++ } ++ ++ /* Allocate and populate the mtd structures */ ++ mtd_parts = kcalloc(le32_to_cpu(smem_parts->len), sizeof(*mtd_parts), ++ GFP_KERNEL); ++ if (!mtd_parts) ++ return -ENOMEM; ++ ++ for (i = 0; i < smem_parts->len; i++) { ++ struct smem_partition *s_part = &smem_parts->parts[i]; ++ struct mtd_partition *m_part = &mtd_parts[i]; ++ ++ m_part->name = s_part->name; ++ m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz); ++ m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz); ++ ++ /* ++ * The last SMEM partition may have its size marked as ++ * something like 0xffffffff, which means "until the end of the ++ * flash device". In this case, truncate it. ++ */ ++ if (m_part->offset + m_part->size > master->size) ++ m_part->size = master->size - m_part->offset; ++ } ++ ++ *pparts = mtd_parts; ++ ++ return smem_parts->len; ++} ++ ++static struct mtd_part_parser qcom_smem_parser = { ++ .owner = THIS_MODULE, ++ .parse_fn = parse_qcom_smem_partitions, ++ .name = "qcom-smem", ++}; ++ ++static int __init qcom_smem_parser_init(void) ++{ ++ register_mtd_parser(&qcom_smem_parser); ++ return 0; ++} ++ ++static void __exit qcom_smem_parser_exit(void) ++{ ++ deregister_mtd_parser(&qcom_smem_parser); ++} ++ ++module_init(qcom_smem_parser_init); ++module_exit(qcom_smem_parser_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Mathieu Olivari "); ++MODULE_DESCRIPTION("Parsing code for SMEM based partition tables"); +--- a/drivers/mtd/Makefile ++++ b/drivers/mtd/Makefile +@@ -16,6 +16,7 @@ obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o + obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o + obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o + obj-$(CONFIG_MTD_MYLOADER_PARTS) += myloader.o ++obj-$(CONFIG_MTD_QCOM_SMEM_PARTS) += qcom_smem_part.o + + # 'Users' - code which presents functionality to userspace. + obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o diff --git a/target/linux/ipq806x/patches-4.1/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch b/target/linux/ipq806x/patches-4.1/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch new file mode 100644 index 0000000..41f91fa --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/110-DT-PCI-qcom-Document-PCIe-devicetree-bindings.patch @@ -0,0 +1,263 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,3/5] DT: PCI: qcom: Document PCIe devicetree bindings +From: Stanimir Varbanov +X-Patchwork-Id: 6326181 +Message-Id: <1430743338-10441-4-git-send-email-svarbanov@mm-sol.com> +To: Rob Herring , Kumar Gala , + Mark Rutland , + Grant Likely , + Bjorn Helgaas , + Kishon Vijay Abraham I , + Russell King , Arnd Bergmann +Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, + linux-pci@vger.kernel.org, Mathieu Olivari , + Srinivas Kandagatla , + Stanimir Varbanov +Date: Mon, 4 May 2015 15:42:16 +0300 + +Document Qualcomm PCIe driver devicetree bindings. + +Signed-off-by: Stanimir Varbanov + +--- +.../devicetree/bindings/pci/qcom,pcie.txt | 231 ++++++++++++++++++++ + 1 files changed, 231 insertions(+), 0 deletions(-) + create mode 100644 Documentation/devicetree/bindings/pci/qcom,pcie.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/pci/qcom,pcie.txt +@@ -0,0 +1,231 @@ ++* Qualcomm PCI express root complex ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: Value shall include ++ - "qcom,pcie-v0" for apq/ipq8064 ++ - "qcom,pcie-v1" for apq8084 ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: Register ranges as listed in the reg-names property ++ ++- reg-names: ++ Usage: required ++ Value type: ++ Definition: Must include the following entries ++ - "parf" Qualcomm specific registers ++ - "dbi" Designware PCIe registers ++ - "elbi" External local bus interface registers ++ - "config" PCIe configuration space ++ ++- device_type: ++ Usage: required ++ Value type: ++ Definition: Should be "pci". As specified in designware-pcie.txt ++ ++- #address-cells: ++ Usage: required ++ Value type: ++ Definition: Should be set to 3. As specified in designware-pcie.txt ++ ++- #size-cells: ++ Usage: required ++ Value type: ++ Definition: Should be set 2. As specified in designware-pcie.txt ++ ++- ranges: ++ Usage: required ++ Value type: ++ Definition: As specified in designware-pcie.txt ++ ++- interrupts: ++ Usage: required ++ Value type: ++ Definition: MSI interrupt ++ ++- interrupt-names: ++ Usage: required ++ Value type: ++ Definition: Should contain "msi" ++ ++- #interrupt-cells: ++ Usage: required ++ Value type: ++ Definition: Should be 1. As specified in designware-pcie.txt ++ ++- interrupt-map-mask: ++ Usage: required ++ Value type: ++ Definition: As specified in designware-pcie.txt ++ ++- interrupt-map: ++ Usage: required ++ Value type: ++ Definition: As specified in designware-pcie.txt ++ ++- clocks: ++ Usage: required ++ Value type: ++ Definition: List of phandle and clock specifier pairs as listed ++ in clock-names property ++ ++- clock-names: ++ Usage: required ++ Value type: ++ Definition: Should contain the following entries ++ * should be populated for v0 and v1 ++ - "iface" Configuration AHB clock ++ ++ * should be populated for v0 ++ - "core" Clocks the pcie hw block ++ - "phy" Clocks the pcie PHY block ++ ++ * should be populated for v1 ++ - "aux" Auxiliary (AUX) clock ++ - "bus_master" Master AXI clock ++ - "bus_slave" Slave AXI clock ++ ++- resets: ++ Usage: required ++ Value type: ++ Definition: List of phandle and reset specifier pairs as listed ++ in reset-names property ++ ++- reset-names: ++ Usage: required ++ Value type: ++ Definition: Should contain the following entries ++ * should be populated for v0 ++ - "axi" AXI reset ++ - "ahb" AHB reset ++ - "por" POR reset ++ - "pci" PCI reset ++ - "phy" PHY reset ++ ++ * should be populated for v1 ++ - "core" Core reset ++ ++- power-domains: ++ Usage: required (for v1 only) ++ Value type: ++ Definition: A phandle and power domain specifier pair to the ++ power domain which is responsible for collapsing ++ and restoring power to the peripheral ++ ++- -supply: ++ Usage: required ++ Value type: ++ Definition: List of phandles to the power supply regulator(s) ++ * should be populated for v0 and v1 ++ - "vdda" core analog power supply ++ ++ * should be populated for v0 ++ - "vdda_phy" analog power supply for PHY ++ - "vdda_refclk" analog power supply for IC which generate ++ reference clock ++ ++- phys: ++ Usage: required (for v1 only) ++ Value type: ++ Definition: List of phandle(s) as listed in phy-names property ++ ++- phy-names: ++ Usage: required (for v1 only) ++ Value type: ++ Definition: Should contain "pciephy" ++ ++- -gpio: ++ Usage: optional ++ Value type: ++ Definition: List of phandle and gpio specifier pairs. Should contain ++ - "perst" PCIe endpoint reset signal line ++ - "pewake" PCIe endpoint wake signal line ++ ++- pinctrl-0: ++ Usage: required ++ Value type: ++ Definition: List of phandles pointing at a pin(s) configuration ++ ++- pinctrl-names ++ Usage: required ++ Value type: ++ Definition: List of names of pinctrl-0 state ++ ++* Example for v0 ++ pcie0: pci@1b500000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b500000 0x1000 ++ 0x1b502000 0x80 ++ 0x1b600000 0x100 ++ 0x0ff00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* I/O */ ++ 0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* memory */ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ clocks = <&gcc PCIE_A_CLK>, ++ <&gcc PCIE_H_CLK>, ++ <&gcc PCIE_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ resets = <&gcc PCIE_ACLK_RESET>, ++ <&gcc PCIE_HCLK_RESET>, ++ <&gcc PCIE_POR_RESET>, ++ <&gcc PCIE_PCI_RESET>, ++ <&gcc PCIE_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ }; ++ ++* Example for v1 ++ pcie0@fc520000 { ++ compatible = "qcom,pcie-v1"; ++ reg = <0xfc520000 0x2000>, ++ <0xff000000 0x1000>, ++ <0xff001000 0x1000>, ++ <0xff002000 0x2000>; ++ reg-names = "parf", "dbi", "elbi", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ranges = <0x81000000 0 0 0xff200000 0 0x00100000 /* I/O */ ++ 0x82000000 0 0x00300000 0xff300000 0 0x00d00000>; /* memory */ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 244 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 245 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 247 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 248 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ clocks = <&gcc GCC_PCIE_0_CFG_AHB_CLK>, ++ <&gcc GCC_PCIE_0_MSTR_AXI_CLK>, ++ <&gcc GCC_PCIE_0_SLV_AXI_CLK>, ++ <&gcc GCC_PCIE_0_AUX_CLK>; ++ clock-names = "iface", "master_bus", "slave_bus", "aux"; ++ resets = <&gcc GCC_PCIE_0_BCR>; ++ reset-names = "core"; ++ power-domains = <&gcc PCIE0_GDSC>; ++ vdda-supply = <&pma8084_l3>; ++ phys = <&pciephy0>; ++ phy-names = "pciephy"; ++ perst-gpio = <&tlmm 70 GPIO_ACTIVE_LOW>; ++ pinctrl-0 = <&pcie0_pins_default>; ++ pinctrl-names = "default"; ++ }; diff --git a/target/linux/ipq806x/patches-4.1/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch b/target/linux/ipq806x/patches-4.1/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch new file mode 100644 index 0000000..dbf16a3 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/111-PCI-qcom-Add-Qualcomm-PCIe-controller-driver.patch @@ -0,0 +1,753 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v2,4/5] PCI: qcom: Add Qualcomm PCIe controller driver +From: Stanimir Varbanov +X-Patchwork-Id: 6326161 +Message-Id: <1430743338-10441-5-git-send-email-svarbanov@mm-sol.com> +To: Rob Herring , Kumar Gala , + Mark Rutland , + Grant Likely , + Bjorn Helgaas , + Kishon Vijay Abraham I , + Russell King , Arnd Bergmann +Cc: linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, devicetree@vger.kernel.org, + linux-pci@vger.kernel.org, Mathieu Olivari , + Srinivas Kandagatla , + Stanimir Varbanov +Date: Mon, 4 May 2015 15:42:17 +0300 + +The PCIe driver reuse the Designware common code for host +and MSI initialization, and also program the Qualcomm +application specific registers. + +Signed-off-by: Stanimir Varbanov + +--- +MAINTAINERS | 7 + + drivers/pci/host/Kconfig | 9 + + drivers/pci/host/Makefile | 1 + + drivers/pci/host/pcie-qcom.c | 677 ++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 694 insertions(+), 0 deletions(-) + create mode 100644 drivers/pci/host/pcie-qcom.c + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -7599,6 +7599,13 @@ L: linux-pci@vger.kernel.org + S: Maintained + F: drivers/pci/host/*spear* + ++PCIE DRIVER FOR QUALCOMM MSM ++M: Stanimir Varbanov ++L: linux-pci@vger.kernel.org ++L: linux-arm-msm@vger.kernel.org ++S: Maintained ++F: drivers/pci/host/*qcom* ++ + PCMCIA SUBSYSTEM + P: Linux PCMCIA Team + L: linux-pcmcia@lists.infradead.org +--- a/drivers/pci/host/Kconfig ++++ b/drivers/pci/host/Kconfig +@@ -125,4 +125,13 @@ config PCIE_IPROC_PLATFORM + Say Y here if you want to use the Broadcom iProc PCIe controller + through the generic platform bus interface + ++config PCIE_QCOM ++ bool "Qualcomm PCIe controller" ++ depends on ARCH_QCOM && OF || (ARM && COMPILE_TEST) ++ select PCIE_DW ++ select PCIEPORTBUS ++ help ++ Say Y here to enable PCIe controller support on Qualcomm SoCs. The ++ PCIe controller use Designware core plus Qualcomm specific hardware ++ wrappers. + endmenu +--- /dev/null ++++ b/drivers/pci/host/pcie-qcom.c +@@ -0,0 +1,677 @@ ++/* ++ * Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pcie-designware.h" ++ ++#define PCIE20_PARF_PHY_CTRL 0x40 ++#define PCIE20_PARF_PHY_REFCLK 0x4C ++#define PCIE20_PARF_DBI_BASE_ADDR 0x168 ++#define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c ++#define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 ++ ++#define PCIE20_ELBI_SYS_CTRL 0x04 ++#define PCIE20_ELBI_SYS_STTS 0x08 ++#define XMLH_LINK_UP BIT(10) ++ ++#define PCIE20_CAP 0x70 ++#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) ++ ++#define PERST_DELAY_MIN_US 1000 ++#define PERST_DELAY_MAX_US 1005 ++ ++#define LINKUP_DELAY_MIN_US 5000 ++#define LINKUP_DELAY_MAX_US 5100 ++#define LINKUP_RETRIES_COUNT 20 ++ ++#define PCIE_V0 0 /* apq8064 */ ++#define PCIE_V1 1 /* apq8084 */ ++ ++struct qcom_pcie_resources_v0 { ++ struct clk *iface_clk; ++ struct clk *core_clk; ++ struct clk *phy_clk; ++ struct reset_control *pci_reset; ++ struct reset_control *axi_reset; ++ struct reset_control *ahb_reset; ++ struct reset_control *por_reset; ++ struct reset_control *phy_reset; ++ struct regulator *vdda; ++ struct regulator *vdda_phy; ++ struct regulator *vdda_refclk; ++}; ++ ++struct qcom_pcie_resources_v1 { ++ struct clk *iface; ++ struct clk *aux; ++ struct clk *master_bus; ++ struct clk *slave_bus; ++ struct reset_control *core; ++ struct regulator *vdda; ++}; ++ ++union pcie_resources { ++ struct qcom_pcie_resources_v0 v0; ++ struct qcom_pcie_resources_v1 v1; ++}; ++ ++struct qcom_pcie { ++ struct pcie_port pp; ++ struct device *dev; ++ union pcie_resources res; ++ void __iomem *parf; ++ void __iomem *dbi; ++ void __iomem *elbi; ++ struct phy *phy; ++ struct gpio_desc *reset; ++ unsigned int version; ++}; ++ ++#define to_qcom_pcie(x) container_of(x, struct qcom_pcie, pp) ++ ++static inline void ++writel_masked(void __iomem *addr, u32 clear_mask, u32 set_mask) ++{ ++ u32 val = readl(addr); ++ ++ val &= ~clear_mask; ++ val |= set_mask; ++ writel(val, addr); ++} ++ ++static void qcom_ep_reset_assert_deassert(struct qcom_pcie *pcie, int assert) ++{ ++ int val, active_low; ++ ++ if (IS_ERR_OR_NULL(pcie->reset)) ++ return; ++ ++ active_low = gpiod_is_active_low(pcie->reset); ++ ++ if (assert) ++ val = !!active_low; ++ else ++ val = !active_low; ++ ++ gpiod_set_value(pcie->reset, val); ++ ++ usleep_range(PERST_DELAY_MIN_US, PERST_DELAY_MAX_US); ++} ++ ++static void qcom_ep_reset_assert(struct qcom_pcie *pcie) ++{ ++ qcom_ep_reset_assert_deassert(pcie, 1); ++} ++ ++static void qcom_ep_reset_deassert(struct qcom_pcie *pcie) ++{ ++ qcom_ep_reset_assert_deassert(pcie, 0); ++} ++ ++static irqreturn_t qcom_pcie_msi_irq_handler(int irq, void *arg) ++{ ++ struct pcie_port *pp = arg; ++ ++ return dw_handle_msi_irq(pp); ++} ++ ++static int qcom_pcie_link_up(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ u32 val = readl(pcie->dbi + PCIE20_CAP_LINKCTRLSTATUS); ++ ++ return val & BIT(29) ? 1 : 0; ++} ++ ++static void qcom_pcie_disable_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ ++ reset_control_assert(res->pci_reset); ++ reset_control_assert(res->axi_reset); ++ reset_control_assert(res->ahb_reset); ++ reset_control_assert(res->por_reset); ++ reset_control_assert(res->pci_reset); ++ clk_disable_unprepare(res->iface_clk); ++ clk_disable_unprepare(res->core_clk); ++ clk_disable_unprepare(res->phy_clk); ++ regulator_disable(res->vdda); ++ regulator_disable(res->vdda_phy); ++ regulator_disable(res->vdda_refclk); ++} ++ ++static void qcom_pcie_disable_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ ++ reset_control_assert(res->core); ++ clk_disable_unprepare(res->slave_bus); ++ clk_disable_unprepare(res->master_bus); ++ clk_disable_unprepare(res->iface); ++ clk_disable_unprepare(res->aux); ++ regulator_disable(res->vdda); ++} ++ ++static int qcom_pcie_enable_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ ret = regulator_enable(res->vdda); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda regulator\n"); ++ return ret; ++ } ++ ++ ret = regulator_enable(res->vdda_refclk); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda_refclk regulator\n"); ++ goto err_refclk; ++ } ++ ++ ret = regulator_enable(res->vdda_phy); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda_phy regulator\n"); ++ goto err_vdda_phy; ++ } ++ ++ ret = clk_prepare_enable(res->iface_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable iface clock\n"); ++ goto err_iface; ++ } ++ ++ ret = clk_prepare_enable(res->core_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable core clock\n"); ++ goto err_clk_core; ++ } ++ ++ ret = clk_prepare_enable(res->phy_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable phy clock\n"); ++ goto err_clk_phy; ++ } ++ ++ ret = reset_control_deassert(res->ahb_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert ahb reset\n"); ++ goto err_reset_ahb; ++ } ++ ++ return 0; ++ ++err_reset_ahb: ++ clk_disable_unprepare(res->phy_clk); ++err_clk_phy: ++ clk_disable_unprepare(res->core_clk); ++err_clk_core: ++ clk_disable_unprepare(res->iface_clk); ++err_iface: ++ regulator_disable(res->vdda_phy); ++err_vdda_phy: ++ regulator_disable(res->vdda_refclk); ++err_refclk: ++ regulator_disable(res->vdda); ++ return ret; ++} ++ ++static int qcom_pcie_enable_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ ret = reset_control_deassert(res->core); ++ if (ret) { ++ dev_err(dev, "cannot deassert core reset\n"); ++ return ret; ++ } ++ ++ ret = clk_prepare_enable(res->aux); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable aux clock\n"); ++ goto err_res; ++ } ++ ++ ret = clk_prepare_enable(res->iface); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable iface clock\n"); ++ goto err_aux; ++ } ++ ++ ret = clk_prepare_enable(res->master_bus); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable master_bus clock\n"); ++ goto err_iface; ++ } ++ ++ ret = clk_prepare_enable(res->slave_bus); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable slave_bus clock\n"); ++ goto err_master; ++ } ++ ++ ret = regulator_enable(res->vdda); ++ if (ret) { ++ dev_err(dev, "cannot enable vdda regulator\n"); ++ goto err_slave; ++ } ++ ++ return 0; ++ ++err_slave: ++ clk_disable_unprepare(res->slave_bus); ++err_master: ++ clk_disable_unprepare(res->master_bus); ++err_iface: ++ clk_disable_unprepare(res->iface); ++err_aux: ++ clk_disable_unprepare(res->aux); ++err_res: ++ reset_control_assert(res->core); ++ ++ return ret; ++} ++ ++static int qcom_pcie_get_resources_v0(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ ++ res->vdda = devm_regulator_get(dev, "vdda"); ++ if (IS_ERR(res->vdda)) ++ return PTR_ERR(res->vdda); ++ ++ res->vdda_phy = devm_regulator_get(dev, "vdda_phy"); ++ if (IS_ERR(res->vdda_phy)) ++ return PTR_ERR(res->vdda_phy); ++ ++ res->vdda_refclk = devm_regulator_get(dev, "vdda_refclk"); ++ if (IS_ERR(res->vdda_refclk)) ++ return PTR_ERR(res->vdda_refclk); ++ ++ res->iface_clk = devm_clk_get(dev, "iface"); ++ if (IS_ERR(res->iface_clk)) ++ return PTR_ERR(res->iface_clk); ++ ++ res->core_clk = devm_clk_get(dev, "core"); ++ if (IS_ERR(res->core_clk)) ++ return PTR_ERR(res->core_clk); ++ ++ res->phy_clk = devm_clk_get(dev, "phy"); ++ if (IS_ERR(res->phy_clk)) ++ return PTR_ERR(res->phy_clk); ++ ++ res->pci_reset = devm_reset_control_get(dev, "pci"); ++ if (IS_ERR(res->pci_reset)) ++ return PTR_ERR(res->pci_reset); ++ ++ res->axi_reset = devm_reset_control_get(dev, "axi"); ++ if (IS_ERR(res->axi_reset)) ++ return PTR_ERR(res->axi_reset); ++ ++ res->ahb_reset = devm_reset_control_get(dev, "ahb"); ++ if (IS_ERR(res->ahb_reset)) ++ return PTR_ERR(res->ahb_reset); ++ ++ res->por_reset = devm_reset_control_get(dev, "por"); ++ if (IS_ERR(res->por_reset)) ++ return PTR_ERR(res->por_reset); ++ ++ res->phy_reset = devm_reset_control_get(dev, "phy"); ++ if (IS_ERR(res->phy_reset)) ++ return PTR_ERR(res->phy_reset); ++ ++ return 0; ++} ++ ++static int qcom_pcie_get_resources_v1(struct qcom_pcie *pcie) ++{ ++ struct qcom_pcie_resources_v1 *res = &pcie->res.v1; ++ struct device *dev = pcie->dev; ++ ++ res->vdda = devm_regulator_get(dev, "vdda"); ++ if (IS_ERR(res->vdda)) ++ return PTR_ERR(res->vdda); ++ ++ res->iface = devm_clk_get(dev, "iface"); ++ if (IS_ERR(res->iface)) ++ return PTR_ERR(res->iface); ++ ++ res->aux = devm_clk_get(dev, "aux"); ++ if (IS_ERR(res->aux) && PTR_ERR(res->aux) == -EPROBE_DEFER) ++ return -EPROBE_DEFER; ++ else if (IS_ERR(res->aux)) ++ res->aux = NULL; ++ ++ res->master_bus = devm_clk_get(dev, "master_bus"); ++ if (IS_ERR(res->master_bus)) ++ return PTR_ERR(res->master_bus); ++ ++ res->slave_bus = devm_clk_get(dev, "slave_bus"); ++ if (IS_ERR(res->slave_bus)) ++ return PTR_ERR(res->slave_bus); ++ ++ res->core = devm_reset_control_get(dev, "core"); ++ if (IS_ERR(res->core)) ++ return PTR_ERR(res->core); ++ ++ return 0; ++} ++ ++static int qcom_pcie_enable_link_training(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ struct device *dev = pp->dev; ++ int retries; ++ u32 val; ++ ++ /* enable link training */ ++ writel_masked(pcie->elbi + PCIE20_ELBI_SYS_CTRL, 0, BIT(0)); ++ ++ /* wait for up to 100ms for the link to come up */ ++ retries = LINKUP_RETRIES_COUNT; ++ do { ++ val = readl(pcie->elbi + PCIE20_ELBI_SYS_STTS); ++ if (val & XMLH_LINK_UP) ++ break; ++ usleep_range(LINKUP_DELAY_MIN_US, LINKUP_DELAY_MAX_US); ++ } while (retries--); ++ ++ if (retries < 0 || !dw_pcie_link_up(pp)) { ++ dev_err(dev, "link initialization failed\n"); ++ return -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static void qcom_pcie_host_init_v1(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ int ret; ++ ++ qcom_ep_reset_assert(pcie); ++ ++ ret = qcom_pcie_enable_resources_v1(pcie); ++ if (ret) ++ return; ++ ++ /* change DBI base address */ ++ writel(0, pcie->parf + PCIE20_PARF_DBI_BASE_ADDR); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ writel_masked(pcie->parf + PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT, ++ 0, BIT(31)); ++ ++ ret = phy_init(pcie->phy); ++ if (ret) ++ goto err_res; ++ ++ ret = phy_power_on(pcie->phy); ++ if (ret) ++ goto err_phy; ++ ++ dw_pcie_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ dw_pcie_msi_init(pp); ++ ++ qcom_ep_reset_deassert(pcie); ++ ++ ret = qcom_pcie_enable_link_training(pp); ++ if (ret) ++ goto err; ++ ++ return; ++ ++err: ++ qcom_ep_reset_assert(pcie); ++ phy_power_off(pcie->phy); ++err_phy: ++ phy_exit(pcie->phy); ++err_res: ++ qcom_pcie_disable_resources_v1(pcie); ++} ++ ++static void qcom_pcie_host_init_v0(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ struct qcom_pcie_resources_v0 *res = &pcie->res.v0; ++ struct device *dev = pcie->dev; ++ int ret; ++ ++ qcom_ep_reset_assert(pcie); ++ ++ ret = qcom_pcie_enable_resources_v0(pcie); ++ if (ret) ++ return; ++ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); ++ ++ /* enable external reference clock */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); ++ ++ ret = reset_control_deassert(res->phy_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert phy reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->pci_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert pci reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->por_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert por reset\n"); ++ return; ++ } ++ ++ ret = reset_control_deassert(res->axi_reset); ++ if (ret) { ++ dev_err(dev, "cannot deassert axi reset\n"); ++ return; ++ } ++ ++ /* wait 150ms for clock acquisition */ ++ usleep_range(10000, 15000); ++ ++ dw_pcie_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) ++ dw_pcie_msi_init(pp); ++ ++ qcom_ep_reset_deassert(pcie); ++ ++ ret = qcom_pcie_enable_link_training(pp); ++ if (ret) ++ goto err; ++ ++ return; ++err: ++ qcom_ep_reset_assert(pcie); ++ qcom_pcie_disable_resources_v0(pcie); ++} ++ ++static void qcom_pcie_host_init(struct pcie_port *pp) ++{ ++ struct qcom_pcie *pcie = to_qcom_pcie(pp); ++ ++ if (pcie->version == PCIE_V0) ++ return qcom_pcie_host_init_v0(pp); ++ else ++ return qcom_pcie_host_init_v1(pp); ++} ++ ++static int ++qcom_pcie_rd_own_conf(struct pcie_port *pp, int where, int size, u32 *val) ++{ ++ /* the device class is not reported correctly from the register */ ++ if (where == PCI_CLASS_REVISION && size == 4) { ++ *val = readl(pp->dbi_base + PCI_CLASS_REVISION); ++ *val &= ~(0xffff << 16); ++ *val |= PCI_CLASS_BRIDGE_PCI << 16; ++ return PCIBIOS_SUCCESSFUL; ++ } ++ ++ return dw_pcie_cfg_read(pp->dbi_base + (where & ~0x3), where, ++ size, val); ++} ++ ++static struct pcie_host_ops qcom_pcie_ops = { ++ .link_up = qcom_pcie_link_up, ++ .host_init = qcom_pcie_host_init, ++ .rd_own_conf = qcom_pcie_rd_own_conf, ++}; ++ ++static const struct of_device_id qcom_pcie_match[] = { ++ { .compatible = "qcom,pcie-v0", .data = (void *)PCIE_V0 }, ++ { .compatible = "qcom,pcie-v1", .data = (void *)PCIE_V1 }, ++ { } ++}; ++ ++static int qcom_pcie_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_device_id *match; ++ struct resource *res; ++ struct qcom_pcie *pcie; ++ struct pcie_port *pp; ++ int ret; ++ ++ match = of_match_node(qcom_pcie_match, dev->of_node); ++ if (!match) ++ return -ENXIO; ++ ++ pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL); ++ if (!pcie) ++ return -ENOMEM; ++ ++ pcie->version = (unsigned int)match->data; ++ ++ pcie->reset = devm_gpiod_get_optional(dev, "perst"); ++ if (IS_ERR(pcie->reset) && PTR_ERR(pcie->reset) == -EPROBE_DEFER) ++ return PTR_ERR(pcie->reset); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "parf"); ++ pcie->parf = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->parf)) ++ return PTR_ERR(pcie->parf); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); ++ pcie->dbi = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->dbi)) ++ return PTR_ERR(pcie->dbi); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "elbi"); ++ pcie->elbi = devm_ioremap_resource(dev, res); ++ if (IS_ERR(pcie->elbi)) ++ return PTR_ERR(pcie->elbi); ++ ++ pcie->phy = devm_phy_optional_get(dev, "pciephy"); ++ if (IS_ERR(pcie->phy)) ++ return PTR_ERR(pcie->phy); ++ ++ pcie->dev = dev; ++ ++ if (pcie->version == PCIE_V0) ++ ret = qcom_pcie_get_resources_v0(pcie); ++ else ++ ret = qcom_pcie_get_resources_v1(pcie); ++ ++ if (ret) ++ return ret; ++ ++ pp = &pcie->pp; ++ pp->dev = dev; ++ pp->dbi_base = pcie->dbi; ++ pp->root_bus_nr = -1; ++ pp->ops = &qcom_pcie_ops; ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI)) { ++ pp->msi_irq = platform_get_irq_byname(pdev, "msi"); ++ if (pp->msi_irq < 0) { ++ dev_err(dev, "cannot get msi irq\n"); ++ return pp->msi_irq; ++ } ++ ++ ret = devm_request_irq(dev, pp->msi_irq, ++ qcom_pcie_msi_irq_handler, ++ IRQF_SHARED, "qcom-pcie-msi", pp); ++ if (ret) { ++ dev_err(dev, "cannot request msi irq\n"); ++ return ret; ++ } ++ } ++ ++ ret = dw_pcie_host_init(pp); ++ if (ret) { ++ dev_err(dev, "cannot initialize host\n"); ++ return ret; ++ } ++ ++ platform_set_drvdata(pdev, pcie); ++ ++ return 0; ++} ++ ++static int qcom_pcie_remove(struct platform_device *pdev) ++{ ++ struct qcom_pcie *pcie = platform_get_drvdata(pdev); ++ ++ qcom_ep_reset_assert(pcie); ++ phy_power_off(pcie->phy); ++ phy_exit(pcie->phy); ++ if (pcie->version == PCIE_V0) ++ qcom_pcie_disable_resources_v0(pcie); ++ else ++ qcom_pcie_disable_resources_v1(pcie); ++ ++ return 0; ++} ++ ++static struct platform_driver qcom_pcie_driver = { ++ .probe = qcom_pcie_probe, ++ .remove = qcom_pcie_remove, ++ .driver = { ++ .name = "qcom-pcie", ++ .of_match_table = qcom_pcie_match, ++ }, ++}; ++ ++module_platform_driver(qcom_pcie_driver); ++ ++MODULE_AUTHOR("Stanimir Varbanov "); ++MODULE_DESCRIPTION("Qualcomm PCIe root complex driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:qcom-pcie"); +--- a/drivers/pci/host/Makefile ++++ b/drivers/pci/host/Makefile +@@ -15,3 +15,4 @@ obj-$(CONFIG_PCI_LAYERSCAPE) += pci-laye + obj-$(CONFIG_PCI_VERSATILE) += pci-versatile.o + obj-$(CONFIG_PCIE_IPROC) += pcie-iproc.o + obj-$(CONFIG_PCIE_IPROC_PLATFORM) += pcie-iproc-platform.o ++obj-$(CONFIG_PCIE_QCOM) += pcie-qcom.o diff --git a/target/linux/ipq806x/patches-4.1/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-4.1/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch new file mode 100644 index 0000000..77cf3c3 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/112-ARM-dts-qcom-add-pcie-nodes-to-ipq806x-platforms.patch @@ -0,0 +1,266 @@ +From 5b40516b2f5fb9b2a7d6d3e2e924f12ec9d183a8 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Tue, 21 Apr 2015 19:01:42 -0700 +Subject: [PATCH 8/9] ARM: dts: qcom: add pcie nodes to ipq806x platforms + +qcom-pcie driver now supports version 0 of the controller. This change +adds the corresponding entries to the IPQ806x dtsi file and +corresponding platform (AP148). + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 30 ++++++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 124 +++++++++++++++++++++++++++++++ + 2 files changed, 154 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -35,6 +35,24 @@ + bias-disable; + }; + ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ + spi_pins: spi_pins { + mux { + pins = "gpio18", "gpio19", "gpio21"; +@@ -91,5 +109,19 @@ + sata@29000000 { + status = "ok"; + }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -30,6 +30,33 @@ + bias-disable; + }; + ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie2_pins: pcie2_pinmux { ++ mux { ++ pins = "gpio63"; ++ function = "pcie3_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ + spi_pins: spi_pins { + mux { + pins = "gpio18", "gpio19", "gpio21"; +@@ -128,5 +155,26 @@ + usb30@1 { + status = "ok"; + }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie2: pci@1b900000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 63 0>; ++ pinctrl-0 = <&pcie2_pins>; ++ pinctrl-names = "default"; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -4,6 +4,8 @@ + #include + #include + #include ++#include ++#include + + / { + model = "Qualcomm IPQ8064"; +@@ -333,6 +335,129 @@ + compatible = "syscon"; + reg = <0x01200600 0x100>; + }; ++ ++ pcie0: pci@1b500000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b500000 0x1000 ++ 0x1b502000 0x80 ++ 0x1b600000 0x100 ++ 0x0ff00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <0>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0 0x0fe00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x00000000 0x08000000 0 0x07e00000>; /* non-prefetchable memory */ ++ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 36 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 37 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 38 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 39 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_A_CLK>, ++ <&gcc PCIE_H_CLK>, ++ <&gcc PCIE_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_ACLK_RESET>, ++ <&gcc PCIE_HCLK_RESET>, ++ <&gcc PCIE_POR_RESET>, ++ <&gcc PCIE_PCI_RESET>, ++ <&gcc PCIE_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ status = "disabled"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b700000 0x1000 ++ 0x1b702000 0x80 ++ 0x1b800000 0x100 ++ 0x31f00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <1>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0 0x31e00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x00000000 0x2e000000 0 0x03e00000>; /* non-prefetchable memory */ ++ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 58 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 59 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 60 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 61 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_1_A_CLK>, ++ <&gcc PCIE_1_H_CLK>, ++ <&gcc PCIE_1_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_1_ACLK_RESET>, ++ <&gcc PCIE_1_HCLK_RESET>, ++ <&gcc PCIE_1_POR_RESET>, ++ <&gcc PCIE_1_PCI_RESET>, ++ <&gcc PCIE_1_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ status = "disabled"; ++ }; ++ ++ pcie2: pci@1b900000 { ++ compatible = "qcom,pcie-v0"; ++ reg = <0x1b900000 0x1000 ++ 0x1b902000 0x80 ++ 0x1ba00000 0x100 ++ 0x35f00000 0x100000>; ++ reg-names = "dbi", "elbi", "parf", "config"; ++ device_type = "pci"; ++ linux,pci-domain = <2>; ++ bus-range = <0x00 0xff>; ++ num-lanes = <1>; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ ++ ranges = <0x81000000 0 0 0x35e00000 0 0x00100000 /* downstream I/O */ ++ 0x82000000 0 0x00000000 0x32000000 0 0x03e00000>; /* non-prefetchable memory */ ++ ++ interrupts = ; ++ interrupt-names = "msi"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 0x7>; ++ interrupt-map = <0 0 0 1 &intc 0 72 IRQ_TYPE_LEVEL_HIGH>, /* int_a */ ++ <0 0 0 2 &intc 0 73 IRQ_TYPE_LEVEL_HIGH>, /* int_b */ ++ <0 0 0 3 &intc 0 74 IRQ_TYPE_LEVEL_HIGH>, /* int_c */ ++ <0 0 0 4 &intc 0 75 IRQ_TYPE_LEVEL_HIGH>; /* int_d */ ++ ++ clocks = <&gcc PCIE_2_A_CLK>, ++ <&gcc PCIE_2_H_CLK>, ++ <&gcc PCIE_2_PHY_CLK>; ++ clock-names = "core", "iface", "phy"; ++ ++ resets = <&gcc PCIE_2_ACLK_RESET>, ++ <&gcc PCIE_2_HCLK_RESET>, ++ <&gcc PCIE_2_POR_RESET>, ++ <&gcc PCIE_2_PCI_RESET>, ++ <&gcc PCIE_2_PHY_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy"; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.1/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch b/target/linux/ipq806x/patches-4.1/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch new file mode 100644 index 0000000..e2d3135 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/113-ARM-qcom-automatically-select-PCI_DOMAINS-if-PCI-is-.patch @@ -0,0 +1,29 @@ +From f004aa1dec6e2e206be025de15b115d60f2b21e3 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Tue, 21 Apr 2015 19:09:07 -0700 +Subject: [PATCH 9/9] ARM: qcom: automatically select PCI_DOMAINS if PCI is + enabled + +If multiple PCIe devices are present in the system, the kernel will +panic at boot time when trying to scan the PCI buses. This happens on +IPQ806x based platforms, which has 3 PCIe ports. + +Enabling this option allows the kernel to assign the pci-domains +according to the device-tree content. This allows multiple PCIe +controllers to coexist in the system. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/mach-qcom/Kconfig | 1 + + 1 file changed, 1 insertion(+) + +--- a/arch/arm/mach-qcom/Kconfig ++++ b/arch/arm/mach-qcom/Kconfig +@@ -5,6 +5,7 @@ menuconfig ARCH_QCOM + select ARM_AMBA + select PINCTRL + select QCOM_SCM if SMP ++ select PCI_DOMAINS if PCI + help + Support for Qualcomm's devicetree based systems. + diff --git a/target/linux/ipq806x/patches-4.1/114-pcie-add-ctlr-init.patch b/target/linux/ipq806x/patches-4.1/114-pcie-add-ctlr-init.patch new file mode 100644 index 0000000..11c9810 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/114-pcie-add-ctlr-init.patch @@ -0,0 +1,278 @@ +--- a/drivers/pci/host/pcie-qcom.c ++++ b/drivers/pci/host/pcie-qcom.c +@@ -29,8 +29,53 @@ + + #include "pcie-designware.h" + ++/* DBI registers */ ++#define PCIE20_CAP 0x70 ++#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) ++ ++#define PCIE20_AXI_MSTR_RESP_COMP_CTRL0 0x818 ++#define PCIE20_AXI_MSTR_RESP_COMP_CTRL1 0x81c ++ ++#define PCIE20_PLR_IATU_VIEWPORT 0x900 ++#define PCIE20_PLR_IATU_REGION_OUTBOUND (0x0 << 31) ++#define PCIE20_PLR_IATU_REGION_INDEX(x) (x << 0) ++ ++#define PCIE20_PLR_IATU_CTRL1 0x904 ++#define PCIE20_PLR_IATU_TYPE_CFG0 (0x4 << 0) ++#define PCIE20_PLR_IATU_TYPE_MEM (0x0 << 0) ++ ++#define PCIE20_PLR_IATU_CTRL2 0x908 ++#define PCIE20_PLR_IATU_ENABLE BIT(31) ++ ++#define PCIE20_PLR_IATU_LBAR 0x90C ++#define PCIE20_PLR_IATU_UBAR 0x910 ++#define PCIE20_PLR_IATU_LAR 0x914 ++#define PCIE20_PLR_IATU_LTAR 0x918 ++#define PCIE20_PLR_IATU_UTAR 0x91c ++ ++#define MSM_PCIE_DEV_CFG_ADDR 0x01000000 ++ ++/* PARF registers */ ++#define PCIE20_PARF_PCS_DEEMPH 0x34 ++#define PCS_DEEMPH_TX_DEEMPH_GEN1(x) (x << 16) ++#define PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(x) (x << 8) ++#define PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(x) (x << 0) ++ ++#define PCIE20_PARF_PCS_SWING 0x38 ++#define PCS_SWING_TX_SWING_FULL(x) (x << 8) ++#define PCS_SWING_TX_SWING_LOW(x) (x << 0) ++ + #define PCIE20_PARF_PHY_CTRL 0x40 ++#define PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK (0x1f << 16) ++#define PHY_CTRL_PHY_TX0_TERM_OFFSET(x) (x << 16) ++ + #define PCIE20_PARF_PHY_REFCLK 0x4C ++#define REF_SSP_EN BIT(16) ++#define REF_USE_PAD BIT(12) ++ ++#define PCIE20_PARF_CONFIG_BITS 0x50 ++#define PHY_RX0_EQ(x) (x << 24) ++ + #define PCIE20_PARF_DBI_BASE_ADDR 0x168 + #define PCIE20_PARF_SLV_ADDR_SPACE_SIZE 0x16c + #define PCIE20_PARF_AXI_MSTR_WR_ADDR_HALT 0x178 +@@ -39,9 +84,6 @@ + #define PCIE20_ELBI_SYS_STTS 0x08 + #define XMLH_LINK_UP BIT(10) + +-#define PCIE20_CAP 0x70 +-#define PCIE20_CAP_LINKCTRLSTATUS (PCIE20_CAP + 0x10) +- + #define PERST_DELAY_MIN_US 1000 + #define PERST_DELAY_MAX_US 1005 + +@@ -56,14 +98,18 @@ struct qcom_pcie_resources_v0 { + struct clk *iface_clk; + struct clk *core_clk; + struct clk *phy_clk; ++ struct clk *aux_clk; ++ struct clk *ref_clk; + struct reset_control *pci_reset; + struct reset_control *axi_reset; + struct reset_control *ahb_reset; + struct reset_control *por_reset; + struct reset_control *phy_reset; ++ struct reset_control *ext_reset; + struct regulator *vdda; + struct regulator *vdda_phy; + struct regulator *vdda_refclk; ++ uint8_t phy_tx0_term_offset; + }; + + struct qcom_pcie_resources_v1 { +@@ -156,10 +202,13 @@ static void qcom_pcie_disable_resources_ + reset_control_assert(res->axi_reset); + reset_control_assert(res->ahb_reset); + reset_control_assert(res->por_reset); +- reset_control_assert(res->pci_reset); ++ reset_control_assert(res->phy_reset); ++ reset_control_assert(res->ext_reset); + clk_disable_unprepare(res->iface_clk); + clk_disable_unprepare(res->core_clk); + clk_disable_unprepare(res->phy_clk); ++ clk_disable_unprepare(res->aux_clk); ++ clk_disable_unprepare(res->ref_clk); + regulator_disable(res->vdda); + regulator_disable(res->vdda_phy); + regulator_disable(res->vdda_refclk); +@@ -201,6 +250,12 @@ static int qcom_pcie_enable_resources_v0 + goto err_vdda_phy; + } + ++ ret = reset_control_deassert(res->ext_reset); ++ if (ret) { ++ dev_err(dev, "cannot assert ext reset\n"); ++ goto err_reset_ext; ++ } ++ + ret = clk_prepare_enable(res->iface_clk); + if (ret) { + dev_err(dev, "cannot prepare/enable iface clock\n"); +@@ -219,6 +274,18 @@ static int qcom_pcie_enable_resources_v0 + goto err_clk_phy; + } + ++ ret = clk_prepare_enable(res->aux_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable aux clock\n"); ++ goto err_clk_aux; ++ } ++ ++ ret = clk_prepare_enable(res->ref_clk); ++ if (ret) { ++ dev_err(dev, "cannot prepare/enable ref clock\n"); ++ goto err_clk_ref; ++ } ++ + ret = reset_control_deassert(res->ahb_reset); + if (ret) { + dev_err(dev, "cannot deassert ahb reset\n"); +@@ -228,12 +295,18 @@ static int qcom_pcie_enable_resources_v0 + return 0; + + err_reset_ahb: ++ clk_disable_unprepare(res->ref_clk); ++err_clk_ref: ++ clk_disable_unprepare(res->aux_clk); ++err_clk_aux: + clk_disable_unprepare(res->phy_clk); + err_clk_phy: + clk_disable_unprepare(res->core_clk); + err_clk_core: + clk_disable_unprepare(res->iface_clk); + err_iface: ++ reset_control_assert(res->ext_reset); ++err_reset_ext: + regulator_disable(res->vdda_phy); + err_vdda_phy: + regulator_disable(res->vdda_refclk); +@@ -329,6 +402,14 @@ static int qcom_pcie_get_resources_v0(st + if (IS_ERR(res->phy_clk)) + return PTR_ERR(res->phy_clk); + ++ res->aux_clk = devm_clk_get(dev, "aux"); ++ if (IS_ERR(res->aux_clk)) ++ return PTR_ERR(res->aux_clk); ++ ++ res->ref_clk = devm_clk_get(dev, "ref"); ++ if (IS_ERR(res->ref_clk)) ++ return PTR_ERR(res->ref_clk); ++ + res->pci_reset = devm_reset_control_get(dev, "pci"); + if (IS_ERR(res->pci_reset)) + return PTR_ERR(res->pci_reset); +@@ -349,6 +430,14 @@ static int qcom_pcie_get_resources_v0(st + if (IS_ERR(res->phy_reset)) + return PTR_ERR(res->phy_reset); + ++ res->ext_reset = devm_reset_control_get(dev, "ext"); ++ if (IS_ERR(res->ext_reset)) ++ return PTR_ERR(res->ext_reset); ++ ++ if (of_property_read_u8(dev->of_node, "phy-tx0-term-offset", ++ &res->phy_tx0_term_offset)) ++ res->phy_tx0_term_offset = 0; ++ + return 0; + } + +@@ -461,6 +550,57 @@ err_res: + qcom_pcie_disable_resources_v1(pcie); + } + ++static void qcom_pcie_prog_viewport_cfg0(struct qcom_pcie *pcie, u32 busdev) ++{ ++ struct pcie_port *pp = &pcie->pp; ++ ++ /* ++ * program and enable address translation region 0 (device config ++ * address space); region type config; ++ * axi config address range to device config address range ++ */ ++ writel(PCIE20_PLR_IATU_REGION_OUTBOUND | ++ PCIE20_PLR_IATU_REGION_INDEX(0), ++ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT); ++ ++ writel(PCIE20_PLR_IATU_TYPE_CFG0, pcie->dbi + PCIE20_PLR_IATU_CTRL1); ++ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2); ++ writel(pp->cfg0_mod_base, pcie->dbi + PCIE20_PLR_IATU_LBAR); ++ writel((pp->cfg0_mod_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR); ++ writel((pp->cfg0_mod_base + pp->cfg0_size - 1), ++ pcie->dbi + PCIE20_PLR_IATU_LAR); ++ writel(busdev, pcie->dbi + PCIE20_PLR_IATU_LTAR); ++ writel(0, pcie->dbi + PCIE20_PLR_IATU_UTAR); ++} ++ ++static void qcom_pcie_prog_viewport_mem2_outbound(struct qcom_pcie *pcie) ++{ ++ struct pcie_port *pp = &pcie->pp; ++ ++ /* ++ * program and enable address translation region 2 (device resource ++ * address space); region type memory; ++ * axi device bar address range to device bar address range ++ */ ++ writel(PCIE20_PLR_IATU_REGION_OUTBOUND | ++ PCIE20_PLR_IATU_REGION_INDEX(2), ++ pcie->dbi + PCIE20_PLR_IATU_VIEWPORT); ++ ++ writel(PCIE20_PLR_IATU_TYPE_MEM, pcie->dbi + PCIE20_PLR_IATU_CTRL1); ++ writel(PCIE20_PLR_IATU_ENABLE, pcie->dbi + PCIE20_PLR_IATU_CTRL2); ++ writel(pp->mem_mod_base, pcie->dbi + PCIE20_PLR_IATU_LBAR); ++ writel((pp->mem_mod_base >> 32), pcie->dbi + PCIE20_PLR_IATU_UBAR); ++ writel(pp->mem_mod_base + pp->mem_size - 1, ++ pcie->dbi + PCIE20_PLR_IATU_LAR); ++ writel(pp->mem_bus_addr, pcie->dbi + PCIE20_PLR_IATU_LTAR); ++ writel(upper_32_bits(pp->mem_bus_addr), ++ pcie->dbi + PCIE20_PLR_IATU_UTAR); ++ ++ /* 1K PCIE buffer setting */ ++ writel(0x3, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL0); ++ writel(0x1, pcie->dbi + PCIE20_AXI_MSTR_RESP_COMP_CTRL1); ++} ++ + static void qcom_pcie_host_init_v0(struct pcie_port *pp) + { + struct qcom_pcie *pcie = to_qcom_pcie(pp); +@@ -476,9 +616,26 @@ static void qcom_pcie_host_init_v0(struc + + writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, BIT(0), 0); + +- /* enable external reference clock */ +- writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, 0, BIT(16)); ++ /* Set Tx termination offset */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_CTRL, ++ PHY_CTRL_PHY_TX0_TERM_OFFSET_MASK, ++ PHY_CTRL_PHY_TX0_TERM_OFFSET(res->phy_tx0_term_offset)); ++ ++ /* PARF programming */ ++ writel(PCS_DEEMPH_TX_DEEMPH_GEN1(0x18) | ++ PCS_DEEMPH_TX_DEEMPH_GEN2_3_5DB(0x18) | ++ PCS_DEEMPH_TX_DEEMPH_GEN2_6DB(0x22), ++ pcie->parf + PCIE20_PARF_PCS_DEEMPH); ++ writel(PCS_SWING_TX_SWING_FULL(0x78) | ++ PCS_SWING_TX_SWING_LOW(0x78), ++ pcie->parf + PCIE20_PARF_PCS_SWING); ++ writel(PHY_RX0_EQ(0x4), pcie->parf + PCIE20_PARF_CONFIG_BITS); ++ ++ /* Enable reference clock */ ++ writel_masked(pcie->parf + PCIE20_PARF_PHY_REFCLK, ++ REF_USE_PAD, REF_SSP_EN); + ++ /* De-assert PHY, PCIe, POR and AXI resets */ + ret = reset_control_deassert(res->phy_reset); + if (ret) { + dev_err(dev, "cannot deassert phy reset\n"); +@@ -517,6 +674,9 @@ static void qcom_pcie_host_init_v0(struc + if (ret) + goto err; + ++ qcom_pcie_prog_viewport_cfg0(pcie, MSM_PCIE_DEV_CFG_ADDR); ++ qcom_pcie_prog_viewport_mem2_outbound(pcie); ++ + return; + err: + qcom_ep_reset_assert(pcie); diff --git a/target/linux/ipq806x/patches-4.1/115-add-pcie-aux-clk-dts.patch b/target/linux/ipq806x/patches-4.1/115-add-pcie-aux-clk-dts.patch new file mode 100644 index 0000000..d01e5a8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/115-add-pcie-aux-clk-dts.patch @@ -0,0 +1,80 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -364,15 +364,21 @@ + + clocks = <&gcc PCIE_A_CLK>, + <&gcc PCIE_H_CLK>, +- <&gcc PCIE_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_PHY_CLK>, ++ <&gcc PCIE_AUX_CLK>, ++ <&gcc PCIE_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_ACLK_RESET>, + <&gcc PCIE_HCLK_RESET>, + <&gcc PCIE_POR_RESET>, + <&gcc PCIE_PCI_RESET>, +- <&gcc PCIE_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_PHY_RESET>, ++ <&gcc PCIE_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + status = "disabled"; + }; +@@ -405,15 +411,21 @@ + + clocks = <&gcc PCIE_1_A_CLK>, + <&gcc PCIE_1_H_CLK>, +- <&gcc PCIE_1_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_1_PHY_CLK>, ++ <&gcc PCIE_1_AUX_CLK>, ++ <&gcc PCIE_1_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_1_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_1_ACLK_RESET>, + <&gcc PCIE_1_HCLK_RESET>, + <&gcc PCIE_1_POR_RESET>, + <&gcc PCIE_1_PCI_RESET>, +- <&gcc PCIE_1_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_1_PHY_RESET>, ++ <&gcc PCIE_1_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + status = "disabled"; + }; +@@ -446,15 +458,21 @@ + + clocks = <&gcc PCIE_2_A_CLK>, + <&gcc PCIE_2_H_CLK>, +- <&gcc PCIE_2_PHY_CLK>; +- clock-names = "core", "iface", "phy"; ++ <&gcc PCIE_2_PHY_CLK>, ++ <&gcc PCIE_2_AUX_CLK>, ++ <&gcc PCIE_2_ALT_REF_CLK>; ++ clock-names = "core", "iface", "phy", "aux", "ref"; ++ ++ assigned-clocks = <&gcc PCIE_2_ALT_REF_CLK>; ++ assigned-clock-rates = <100000000>; + + resets = <&gcc PCIE_2_ACLK_RESET>, + <&gcc PCIE_2_HCLK_RESET>, + <&gcc PCIE_2_POR_RESET>, + <&gcc PCIE_2_PCI_RESET>, +- <&gcc PCIE_2_PHY_RESET>; +- reset-names = "axi", "ahb", "por", "pci", "phy"; ++ <&gcc PCIE_2_PHY_RESET>, ++ <&gcc PCIE_2_EXT_RESET>; ++ reset-names = "axi", "ahb", "por", "pci", "phy", "ext"; + + status = "disabled"; + }; diff --git a/target/linux/ipq806x/patches-4.1/126-add-rpm-to-ipq8064-dts.patch b/target/linux/ipq806x/patches-4.1/126-add-rpm-to-ipq8064-dts.patch new file mode 100644 index 0000000..be80f4c --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/126-add-rpm-to-ipq8064-dts.patch @@ -0,0 +1,87 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -2,6 +2,7 @@ + + #include "skeleton.dtsi" + #include ++#include + #include + #include + #include +@@ -92,6 +93,63 @@ + reg-names = "lpass-lpaif"; + }; + ++ rpm@108000 { ++ compatible = "qcom,rpm-ipq8064"; ++ reg = <0x108000 0x1000>; ++ qcom,ipc = <&l2cc 0x8 2>; ++ ++ interrupts = <0 19 0>, ++ <0 21 0>, ++ <0 22 0>; ++ interrupt-names = "ack", ++ "err", ++ "wakeup"; ++ ++ #address-cells = <1>; ++ #size-cells = <0>; ++ ++ smb208_s1a: smb208-s1a { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = <1050000>; ++ regulator-max-microvolt = <1150000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ ++ }; ++ ++ smb208_s1b: smb208-s1b { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = <1050000>; ++ regulator-max-microvolt = <1150000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ ++ smb208_s2a: smb208-s2a { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = < 800000>; ++ regulator-max-microvolt = <1250000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ ++ smb208_s2b: smb208-s2b { ++ compatible = "qcom,rpm-smb208"; ++ reg = ; ++ ++ regulator-min-microvolt = < 800000>; ++ regulator-max-microvolt = <1250000>; ++ ++ qcom,switch-mode-frequency = <1200000>; ++ }; ++ }; ++ + qcom_pinmux: pinmux@800000 { + compatible = "qcom,ipq8064-pinctrl"; + reg = <0x800000 0x4000>; +@@ -136,6 +194,12 @@ + reg = <0x02098000 0x1000>, <0x02008000 0x1000>; + }; + ++ l2cc: clock-controller@2011000 { ++ compatible = "qcom,kpss-gcc", "syscon"; ++ reg = <0x2011000 0x1000>; ++ clock-output-names = "acpu_l2_aux"; ++ }; ++ + saw0: regulator@2089000 { + compatible = "qcom,saw2"; + reg = <0x02089000 0x1000>, <0x02009000 0x1000>; diff --git a/target/linux/ipq806x/patches-4.1/133-ARM-Add-Krait-L2-register-accessor-functions.patch b/target/linux/ipq806x/patches-4.1/133-ARM-Add-Krait-L2-register-accessor-functions.patch new file mode 100644 index 0000000..36a92c8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/133-ARM-Add-Krait-L2-register-accessor-functions.patch @@ -0,0 +1,144 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,01/13] ARM: Add Krait L2 register accessor functions +From: Stephen Boyd +X-Patchwork-Id: 6063051 +Message-Id: <1426920332-9340-2-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , + Mark Rutland , Russell King , + Courtney Cavin +Date: Fri, 20 Mar 2015 23:45:20 -0700 + +Krait CPUs have a handful of L2 cache controller registers that +live behind a cp15 based indirection register. First you program +the indirection register (l2cpselr) to point the L2 'window' +register (l2cpdr) at what you want to read/write. Then you +read/write the 'window' register to do what you want. The +l2cpselr register is not banked per-cpu so we must lock around +accesses to it to prevent other CPUs from re-pointing l2cpdr +underneath us. + +Cc: Mark Rutland +Cc: Russell King +Cc: Courtney Cavin +Signed-off-by: Stephen Boyd + +--- +arch/arm/common/Kconfig | 3 ++ + arch/arm/common/Makefile | 1 + + arch/arm/common/krait-l2-accessors.c | 58 +++++++++++++++++++++++++++++++ + arch/arm/include/asm/krait-l2-accessors.h | 20 +++++++++++ + 4 files changed, 82 insertions(+) + create mode 100644 arch/arm/common/krait-l2-accessors.c + create mode 100644 arch/arm/include/asm/krait-l2-accessors.h + +--- a/arch/arm/common/Kconfig ++++ b/arch/arm/common/Kconfig +@@ -9,6 +9,9 @@ config DMABOUNCE + bool + select ZONE_DMA + ++config KRAIT_L2_ACCESSORS ++ bool ++ + config SHARP_LOCOMO + bool + +--- a/arch/arm/common/Makefile ++++ b/arch/arm/common/Makefile +@@ -7,6 +7,7 @@ obj-y += firmware.o + obj-$(CONFIG_ICST) += icst.o + obj-$(CONFIG_SA1111) += sa1111.o + obj-$(CONFIG_DMABOUNCE) += dmabounce.o ++obj-$(CONFIG_KRAIT_L2_ACCESSORS) += krait-l2-accessors.o + obj-$(CONFIG_SHARP_LOCOMO) += locomo.o + obj-$(CONFIG_SHARP_PARAM) += sharpsl_param.o + obj-$(CONFIG_SHARP_SCOOP) += scoop.o +--- /dev/null ++++ b/arch/arm/common/krait-l2-accessors.c +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++ ++#include ++#include ++ ++static DEFINE_RAW_SPINLOCK(krait_l2_lock); ++ ++void krait_set_l2_indirect_reg(u32 addr, u32 val) ++{ ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&krait_l2_lock, flags); ++ /* ++ * Select the L2 window by poking l2cpselr, then write to the window ++ * via l2cpdr. ++ */ ++ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); ++ isb(); ++ asm volatile ("mcr p15, 3, %0, c15, c0, 7 @ l2cpdr" : : "r" (val)); ++ isb(); ++ ++ raw_spin_unlock_irqrestore(&krait_l2_lock, flags); ++} ++EXPORT_SYMBOL(krait_set_l2_indirect_reg); ++ ++u32 krait_get_l2_indirect_reg(u32 addr) ++{ ++ u32 val; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&krait_l2_lock, flags); ++ /* ++ * Select the L2 window by poking l2cpselr, then read from the window ++ * via l2cpdr. ++ */ ++ asm volatile ("mcr p15, 3, %0, c15, c0, 6 @ l2cpselr" : : "r" (addr)); ++ isb(); ++ asm volatile ("mrc p15, 3, %0, c15, c0, 7 @ l2cpdr" : "=r" (val)); ++ ++ raw_spin_unlock_irqrestore(&krait_l2_lock, flags); ++ ++ return val; ++} ++EXPORT_SYMBOL(krait_get_l2_indirect_reg); +--- /dev/null ++++ b/arch/arm/include/asm/krait-l2-accessors.h +@@ -0,0 +1,20 @@ ++/* ++ * Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __ASMARM_KRAIT_L2_ACCESSORS_H ++#define __ASMARM_KRAIT_L2_ACCESSORS_H ++ ++extern void krait_set_l2_indirect_reg(u32 addr, u32 val); ++extern u32 krait_get_l2_indirect_reg(u32 addr); ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.1/134-clk-mux-Split-out-register-accessors-for-reuse.patch b/target/linux/ipq806x/patches-4.1/134-clk-mux-Split-out-register-accessors-for-reuse.patch new file mode 100644 index 0000000..3a475fb --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/134-clk-mux-Split-out-register-accessors-for-reuse.patch @@ -0,0 +1,192 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,02/13] clk: mux: Split out register accessors for reuse +From: Stephen Boyd +X-Patchwork-Id: 6063111 +Message-Id: <1426920332-9340-3-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:21 -0700 + +We want to reuse the logic in clk-mux.c for other clock drivers +that don't use readl as register accessors. Fortunately, there +really isn't much to the mux code besides the table indirection +and quirk flags if you assume any bit shifting and masking has +been done already. Pull that logic out into reusable functions +that operate on an optional table and some flags so that other +drivers can use the same logic. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/clk-mux.c | 76 +++++++++++++++++++++++++++----------------- + include/linux/clk-provider.h | 9 ++++-- + 2 files changed, 54 insertions(+), 31 deletions(-) + +--- a/drivers/clk/clk-mux.c ++++ b/drivers/clk/clk-mux.c +@@ -29,35 +29,24 @@ + + #define to_clk_mux(_hw) container_of(_hw, struct clk_mux, hw) + +-static u8 clk_mux_get_parent(struct clk_hw *hw) ++unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val, ++ unsigned int *table, unsigned long flags) + { +- struct clk_mux *mux = to_clk_mux(hw); + int num_parents = __clk_get_num_parents(hw->clk); +- u32 val; + +- /* +- * FIXME need a mux-specific flag to determine if val is bitwise or numeric +- * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 +- * to 0x7 (index starts at one) +- * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so +- * val = 0x4 really means "bit 2, index starts at bit 0" +- */ +- val = clk_readl(mux->reg) >> mux->shift; +- val &= mux->mask; +- +- if (mux->table) { ++ if (table) { + int i; + + for (i = 0; i < num_parents; i++) +- if (mux->table[i] == val) ++ if (table[i] == val) + return i; + return -EINVAL; + } + +- if (val && (mux->flags & CLK_MUX_INDEX_BIT)) ++ if (val && (flags & CLK_MUX_INDEX_BIT)) + val = ffs(val) - 1; + +- if (val && (mux->flags & CLK_MUX_INDEX_ONE)) ++ if (val && (flags & CLK_MUX_INDEX_ONE)) + val--; + + if (val >= num_parents) +@@ -65,24 +54,53 @@ static u8 clk_mux_get_parent(struct clk_ + + return val; + } ++EXPORT_SYMBOL_GPL(clk_mux_get_parent); + +-static int clk_mux_set_parent(struct clk_hw *hw, u8 index) ++static u8 _clk_mux_get_parent(struct clk_hw *hw) + { + struct clk_mux *mux = to_clk_mux(hw); + u32 val; +- unsigned long flags = 0; + +- if (mux->table) +- index = mux->table[index]; ++ /* ++ * FIXME need a mux-specific flag to determine if val is bitwise or numeric ++ * e.g. sys_clkin_ck's clksel field is 3 bits wide, but ranges from 0x1 ++ * to 0x7 (index starts at one) ++ * OTOH, pmd_trace_clk_mux_ck uses a separate bit for each clock, so ++ * val = 0x4 really means "bit 2, index starts at bit 0" ++ */ ++ val = clk_readl(mux->reg) >> mux->shift; ++ val &= mux->mask; ++ ++ return clk_mux_get_parent(hw, val, mux->table, mux->flags); ++} + +- else { +- if (mux->flags & CLK_MUX_INDEX_BIT) +- index = 1 << index; ++unsigned int clk_mux_reindex(u8 index, unsigned int *table, ++ unsigned long flags) ++{ ++ unsigned int val = index; + +- if (mux->flags & CLK_MUX_INDEX_ONE) +- index++; ++ if (table) { ++ val = table[val]; ++ } else { ++ if (flags & CLK_MUX_INDEX_BIT) ++ val = 1 << index; ++ ++ if (flags & CLK_MUX_INDEX_ONE) ++ val++; + } + ++ return val; ++} ++EXPORT_SYMBOL_GPL(clk_mux_reindex); ++ ++static int clk_mux_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct clk_mux *mux = to_clk_mux(hw); ++ u32 val; ++ unsigned long flags = 0; ++ ++ index = clk_mux_reindex(index, mux->table, mux->flags); ++ + if (mux->lock) + spin_lock_irqsave(mux->lock, flags); + +@@ -102,21 +120,21 @@ static int clk_mux_set_parent(struct clk + } + + const struct clk_ops clk_mux_ops = { +- .get_parent = clk_mux_get_parent, ++ .get_parent = _clk_mux_get_parent, + .set_parent = clk_mux_set_parent, + .determine_rate = __clk_mux_determine_rate, + }; + EXPORT_SYMBOL_GPL(clk_mux_ops); + + const struct clk_ops clk_mux_ro_ops = { +- .get_parent = clk_mux_get_parent, ++ .get_parent = _clk_mux_get_parent, + }; + EXPORT_SYMBOL_GPL(clk_mux_ro_ops); + + struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, +- u8 clk_mux_flags, u32 *table, spinlock_t *lock) ++ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock) + { + struct clk_mux *mux; + struct clk *clk; +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -409,7 +409,7 @@ void clk_unregister_divider(struct clk * + struct clk_mux { + struct clk_hw hw; + void __iomem *reg; +- u32 *table; ++ unsigned int *table; + u32 mask; + u8 shift; + u8 flags; +@@ -425,6 +425,11 @@ struct clk_mux { + extern const struct clk_ops clk_mux_ops; + extern const struct clk_ops clk_mux_ro_ops; + ++unsigned int clk_mux_get_parent(struct clk_hw *hw, unsigned int val, ++ unsigned int *table, unsigned long flags); ++unsigned int clk_mux_reindex(u8 index, unsigned int *table, ++ unsigned long flags); ++ + struct clk *clk_register_mux(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u8 width, +@@ -433,7 +438,7 @@ struct clk *clk_register_mux(struct devi + struct clk *clk_register_mux_table(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, unsigned long flags, + void __iomem *reg, u8 shift, u32 mask, +- u8 clk_mux_flags, u32 *table, spinlock_t *lock); ++ u8 clk_mux_flags, unsigned int *table, spinlock_t *lock); + + void clk_unregister_mux(struct clk *clk); + diff --git a/target/linux/ipq806x/patches-4.1/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch b/target/linux/ipq806x/patches-4.1/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch new file mode 100644 index 0000000..143e079 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/135-clk-Avoid-sending-high-rates-to-downstream-clocks-during-set_rate.patch @@ -0,0 +1,130 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3, 03/13] clk: Avoid sending high rates to downstream clocks during + set_rate +From: Stephen Boyd +X-Patchwork-Id: 6063271 +Message-Id: <1426920332-9340-4-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:22 -0700 + +If a clock is on and we call clk_set_rate() on it we may get into +a situation where the clock temporarily increases in rate +dramatically while we walk the tree and call .set_rate() ops. For +example, consider a case where a PLL feeds into a divider. +Initially the divider is set to divide by 1 and the PLL is +running fairly slow (100MHz). The downstream consumer of the +divider output can only handle rates =< 400 MHz, but the divider +can only choose between divisors of 1 and 4. + + +-----+ +----------------+ + | PLL |-->| div 1 or div 4 |---> consumer device + +-----+ +----------------+ + +To achieve a rate of 400MHz on the output of the divider, we +would have to set the rate of the PLL to 1.6 GHz and then divide +it by 4. The current code would set the PLL to 1.6GHz first while +the divider is still set to 1, thus causing the downstream +consumer of the clock to receive a few clock cycles of 1.6GHz +clock (far beyond it's maximum acceptable rate). We should be +changing the divider first before increasing the PLL rate to +avoid this problem. + +Therefore, set the rate of any child clocks that are increasing +in rate from their current rate so that they can increase their +dividers if necessary. We assume that there isn't such a thing as +minimum rate requirements. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/clk.c | 34 ++++++++++++++++++++++------------ + 1 file changed, 22 insertions(+), 12 deletions(-) + +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -1744,21 +1744,24 @@ static struct clk_core *clk_propagate_ra + * walk down a subtree and set the new rates notifying the rate + * change on the way + */ +-static void clk_change_rate(struct clk_core *clk) ++static void ++clk_change_rate(struct clk_core *clk, unsigned long best_parent_rate) + { + struct clk_core *child; + struct hlist_node *tmp; + unsigned long old_rate; +- unsigned long best_parent_rate = 0; + bool skip_set_rate = false; + struct clk_core *old_parent; + +- old_rate = clk->rate; ++ hlist_for_each_entry(child, &clk->children, child_node) { ++ /* Skip children who will be reparented to another clock */ ++ if (child->new_parent && child->new_parent != clk) ++ continue; ++ if (child->new_rate > child->rate) ++ clk_change_rate(child, clk->new_rate); ++ } + +- if (clk->new_parent) +- best_parent_rate = clk->new_parent->rate; +- else if (clk->parent) +- best_parent_rate = clk->parent->rate; ++ old_rate = clk->rate; + + if (clk->new_parent && clk->new_parent != clk->parent) { + old_parent = __clk_set_parent_before(clk, clk->new_parent); +@@ -1784,7 +1787,7 @@ static void clk_change_rate(struct clk_c + + trace_clk_set_rate_complete(clk, clk->new_rate); + +- clk->rate = clk_recalc(clk, best_parent_rate); ++ clk->rate = clk->new_rate; + + if (clk->notifier_count && old_rate != clk->rate) + __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate); +@@ -1797,12 +1800,13 @@ static void clk_change_rate(struct clk_c + /* Skip children who will be reparented to another clock */ + if (child->new_parent && child->new_parent != clk) + continue; +- clk_change_rate(child); ++ if (child->new_rate != child->rate) ++ clk_change_rate(child, clk->new_rate); + } + + /* handle the new child who might not be in clk->children yet */ +- if (clk->new_child) +- clk_change_rate(clk->new_child); ++ if (clk->new_child && clk->new_child->new_rate != clk->new_child->rate) ++ clk_change_rate(clk->new_child, clk->new_rate); + } + + static int clk_core_set_rate_nolock(struct clk_core *clk, +@@ -1811,6 +1815,7 @@ static int clk_core_set_rate_nolock(stru + struct clk_core *top, *fail_clk; + unsigned long rate = req_rate; + int ret = 0; ++ unsigned long parent_rate; + + if (!clk) + return 0; +@@ -1836,8 +1841,13 @@ static int clk_core_set_rate_nolock(stru + return -EBUSY; + } + ++ if (top->parent) ++ parent_rate = top->parent->rate; ++ else ++ parent_rate = 0; ++ + /* change the rates */ +- clk_change_rate(top); ++ clk_change_rate(top, parent_rate); + + clk->req_rate = req_rate; + diff --git a/target/linux/ipq806x/patches-4.1/136-clk-Add-safe-switch-hook.patch b/target/linux/ipq806x/patches-4.1/136-clk-Add-safe-switch-hook.patch new file mode 100644 index 0000000..b0d89a9 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/136-clk-Add-safe-switch-hook.patch @@ -0,0 +1,164 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,04/13] clk: Add safe switch hook +From: Stephen Boyd +X-Patchwork-Id: 6063211 +Message-Id: <1426920332-9340-5-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:23 -0700 + +Sometimes clocks can't accept their parent source turning off +while the source is reprogrammed to a different rate. Most +notably CPU clocks require a way to switch away from the current +PLL they're running on, reprogram that PLL to a new rate, and +then switch back to the PLL with the new rate once they're done. +Add a hook that drivers can implement allowing them to return a +'safe parent' that they can switch their parent to while the +upstream source is reprogrammed to support this. + +Signed-off-by: Stephen Boyd + +--- +This patch is good enough for Krait, but soon I'll need to +support a "safe rate" where we ask a clock what rate it needs to be running +at to be sure it's within voltage constraints. Right now safe parent +handles that problem on Krait, but on other platforms it won't work. + + drivers/clk/clk.c | 61 ++++++++++++++++++++++++++++++++++++++------ + include/linux/clk-provider.h | 1 + + 2 files changed, 54 insertions(+), 8 deletions(-) + +--- a/drivers/clk/clk.c ++++ b/drivers/clk/clk.c +@@ -56,9 +56,12 @@ struct clk_core { + struct clk_core **parents; + u8 num_parents; + u8 new_parent_index; ++ u8 safe_parent_index; + unsigned long rate; + unsigned long req_rate; ++ unsigned long old_rate; + unsigned long new_rate; ++ struct clk_core *safe_parent; + struct clk_core *new_parent; + struct clk_core *new_child; + unsigned long flags; +@@ -1596,7 +1599,8 @@ out: + static void clk_calc_subtree(struct clk_core *clk, unsigned long new_rate, + struct clk_core *new_parent, u8 p_index) + { +- struct clk_core *child; ++ struct clk_core *child, *parent; ++ struct clk_hw *parent_hw; + + clk->new_rate = new_rate; + clk->new_parent = new_parent; +@@ -1606,6 +1610,18 @@ static void clk_calc_subtree(struct clk_ + if (new_parent && new_parent != clk->parent) + new_parent->new_child = clk; + ++ if (clk->ops->get_safe_parent) { ++ parent_hw = clk->ops->get_safe_parent(clk->hw); ++ if (parent_hw) { ++ parent = parent_hw->core; ++ p_index = clk_fetch_parent_index(clk, parent); ++ clk->safe_parent_index = p_index; ++ clk->safe_parent = parent; ++ } ++ } else { ++ clk->safe_parent = NULL; ++ } ++ + hlist_for_each_entry(child, &clk->children, child_node) { + child->new_rate = clk_recalc(child, new_rate); + clk_calc_subtree(child, child->new_rate, NULL, 0); +@@ -1710,14 +1726,43 @@ static struct clk_core *clk_propagate_ra + unsigned long event) + { + struct clk_core *child, *tmp_clk, *fail_clk = NULL; ++ struct clk_core *old_parent; + int ret = NOTIFY_DONE; + +- if (clk->rate == clk->new_rate) ++ if (clk->rate == clk->new_rate && event != POST_RATE_CHANGE) + return NULL; + ++ switch (event) { ++ case PRE_RATE_CHANGE: ++ if (clk->safe_parent) ++ clk->ops->set_parent(clk->hw, clk->safe_parent_index); ++ clk->old_rate = clk->rate; ++ break; ++ case POST_RATE_CHANGE: ++ if (clk->safe_parent) { ++ old_parent = __clk_set_parent_before(clk, ++ clk->new_parent); ++ if (clk->ops->set_rate_and_parent) { ++ clk->ops->set_rate_and_parent(clk->hw, ++ clk->new_rate, ++ clk->new_parent ? ++ clk->new_parent->rate : 0, ++ clk->new_parent_index); ++ } else if (clk->ops->set_parent) { ++ clk->ops->set_parent(clk->hw, ++ clk->new_parent_index); ++ } ++ __clk_set_parent_after(clk, clk->new_parent, ++ old_parent); ++ } ++ break; ++ } ++ + if (clk->notifier_count) { +- ret = __clk_notify(clk, event, clk->rate, clk->new_rate); +- if (ret & NOTIFY_STOP_MASK) ++ if (event != POST_RATE_CHANGE || clk->old_rate != clk->rate) ++ ret = __clk_notify(clk, event, clk->old_rate, ++ clk->new_rate); ++ if (ret & NOTIFY_STOP_MASK && event != POST_RATE_CHANGE) + fail_clk = clk; + } + +@@ -1763,7 +1808,8 @@ clk_change_rate(struct clk_core *clk, un + + old_rate = clk->rate; + +- if (clk->new_parent && clk->new_parent != clk->parent) { ++ if (clk->new_parent && clk->new_parent != clk->parent && ++ !clk->safe_parent) { + old_parent = __clk_set_parent_before(clk, clk->new_parent); + trace_clk_set_parent(clk, clk->new_parent); + +@@ -1789,9 +1835,6 @@ clk_change_rate(struct clk_core *clk, un + + clk->rate = clk->new_rate; + +- if (clk->notifier_count && old_rate != clk->rate) +- __clk_notify(clk, POST_RATE_CHANGE, old_rate, clk->rate); +- + /* + * Use safe iteration, as change_rate can actually swap parents + * for certain clock types. +@@ -1851,6 +1894,8 @@ static int clk_core_set_rate_nolock(stru + + clk->req_rate = req_rate; + ++ clk_propagate_rate_change(top, POST_RATE_CHANGE); ++ + return ret; + } + +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -183,6 +183,7 @@ struct clk_ops { + struct clk_hw **best_parent_hw); + int (*set_parent)(struct clk_hw *hw, u8 index); + u8 (*get_parent)(struct clk_hw *hw); ++ struct clk_hw *(*get_safe_parent)(struct clk_hw *hw); + int (*set_rate)(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); + int (*set_rate_and_parent)(struct clk_hw *hw, diff --git a/target/linux/ipq806x/patches-4.1/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch b/target/linux/ipq806x/patches-4.1/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch new file mode 100644 index 0000000..6fad6e8 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/137-clk-qcom-Add-support-for-High-Frequency-PLLs-HFPLLs.patch @@ -0,0 +1,351 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,05/13] clk: qcom: Add support for High-Frequency PLLs (HFPLLs) +From: Stephen Boyd +X-Patchwork-Id: 6063261 +Message-Id: <1426920332-9340-6-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:24 -0700 + +HFPLLs are the main frequency source for Krait CPU clocks. Add +support for changing the rate of these PLLs. + +Signed-off-by: Stephen Boyd + +--- +I'd really like to get rid of __clk_hfpll_init_once() if possible... + + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/clk-hfpll.c | 253 +++++++++++++++++++++++++++++++++++++++++++ + drivers/clk/qcom/clk-hfpll.h | 54 +++++++++ + 3 files changed, 308 insertions(+) + create mode 100644 drivers/clk/qcom/clk-hfpll.c + create mode 100644 drivers/clk/qcom/clk-hfpll.h + +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o + clk-qcom-y += clk-branch.o + clk-qcom-y += clk-regmap-divider.o + clk-qcom-y += clk-regmap-mux.o ++clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + + obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o +--- /dev/null ++++ b/drivers/clk/qcom/clk-hfpll.c +@@ -0,0 +1,253 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clk-regmap.h" ++#include "clk-hfpll.h" ++ ++#define PLL_OUTCTRL BIT(0) ++#define PLL_BYPASSNL BIT(1) ++#define PLL_RESET_N BIT(2) ++ ++/* Initialize a HFPLL at a given rate and enable it. */ ++static void __clk_hfpll_init_once(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ ++ if (likely(h->init_done)) ++ return; ++ ++ /* Configure PLL parameters for integer mode. */ ++ if (hd->config_val) ++ regmap_write(regmap, hd->config_reg, hd->config_val); ++ regmap_write(regmap, hd->m_reg, 0); ++ regmap_write(regmap, hd->n_reg, 1); ++ ++ if (hd->user_reg) { ++ u32 regval = hd->user_val; ++ unsigned long rate; ++ ++ rate = __clk_get_rate(hw->clk); ++ ++ /* Pick the right VCO. */ ++ if (hd->user_vco_mask && rate > hd->low_vco_max_rate) ++ regval |= hd->user_vco_mask; ++ regmap_write(regmap, hd->user_reg, regval); ++ } ++ ++ if (hd->droop_reg) ++ regmap_write(regmap, hd->droop_reg, hd->droop_val); ++ ++ h->init_done = true; ++} ++ ++static void __clk_hfpll_enable(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 val; ++ ++ __clk_hfpll_init_once(hw); ++ ++ /* Disable PLL bypass mode. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL); ++ ++ /* ++ * H/W requires a 5us delay between disabling the bypass and ++ * de-asserting the reset. Delay 10us just to be safe. ++ */ ++ udelay(10); ++ ++ /* De-assert active-low PLL reset. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N); ++ ++ /* Wait for PLL to lock. */ ++ if (hd->status_reg) { ++ do { ++ regmap_read(regmap, hd->status_reg, &val); ++ } while (!(val & BIT(hd->lock_bit))); ++ } else { ++ udelay(60); ++ } ++ ++ /* Enable PLL output. */ ++ regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL); ++} ++ ++/* Enable an already-configured HFPLL. */ ++static int clk_hfpll_enable(struct clk_hw *hw) ++{ ++ unsigned long flags; ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ regmap_read(regmap, hd->mode_reg, &mode); ++ if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL))) ++ __clk_hfpll_enable(hw); ++ spin_unlock_irqrestore(&h->lock, flags); ++ ++ return 0; ++} ++ ++static void __clk_hfpll_disable(struct clk_hfpll *h) ++{ ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ ++ /* ++ * Disable the PLL output, disable test mode, enable the bypass mode, ++ * and assert the reset. ++ */ ++ regmap_update_bits(regmap, hd->mode_reg, ++ PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0); ++} ++ ++static void clk_hfpll_disable(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ __clk_hfpll_disable(h); ++ spin_unlock_irqrestore(&h->lock, flags); ++} ++ ++static long clk_hfpll_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ unsigned long rrate; ++ ++ rate = clamp(rate, hd->min_rate, hd->max_rate); ++ ++ rrate = DIV_ROUND_UP(rate, *parent_rate) * *parent_rate; ++ if (rrate > hd->max_rate) ++ rrate -= *parent_rate; ++ ++ return rrate; ++} ++ ++/* ++ * For optimization reasons, assumes no downstream clocks are actively using ++ * it. ++ */ ++static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ unsigned long flags; ++ u32 l_val, val; ++ bool enabled; ++ ++ l_val = rate / parent_rate; ++ ++ spin_lock_irqsave(&h->lock, flags); ++ ++ enabled = __clk_is_enabled(hw->clk); ++ if (enabled) ++ __clk_hfpll_disable(h); ++ ++ /* Pick the right VCO. */ ++ if (hd->user_reg && hd->user_vco_mask) { ++ regmap_read(regmap, hd->user_reg, &val); ++ if (rate <= hd->low_vco_max_rate) ++ val &= ~hd->user_vco_mask; ++ else ++ val |= hd->user_vco_mask; ++ regmap_write(regmap, hd->user_reg, val); ++ } ++ ++ regmap_write(regmap, hd->l_reg, l_val); ++ ++ if (enabled) ++ __clk_hfpll_enable(hw); ++ ++ spin_unlock_irqrestore(&h->lock, flags); ++ ++ return 0; ++} ++ ++static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 l_val; ++ ++ regmap_read(regmap, hd->l_reg, &l_val); ++ ++ return l_val * parent_rate; ++} ++ ++static void clk_hfpll_init(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode, status; ++ ++ regmap_read(regmap, hd->mode_reg, &mode); ++ if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) { ++ __clk_hfpll_init_once(hw); ++ return; ++ } ++ ++ if (hd->status_reg) { ++ regmap_read(regmap, hd->status_reg, &status); ++ if (!(status & BIT(hd->lock_bit))) { ++ WARN(1, "HFPLL %s is ON, but not locked!\n", ++ __clk_get_name(hw->clk)); ++ clk_hfpll_disable(hw); ++ __clk_hfpll_init_once(hw); ++ } ++ } ++} ++ ++static int hfpll_is_enabled(struct clk_hw *hw) ++{ ++ struct clk_hfpll *h = to_clk_hfpll(hw); ++ struct hfpll_data const *hd = h->d; ++ struct regmap *regmap = h->clkr.regmap; ++ u32 mode; ++ ++ regmap_read(regmap, hd->mode_reg, &mode); ++ mode &= 0x7; ++ return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL); ++} ++ ++const struct clk_ops clk_ops_hfpll = { ++ .enable = clk_hfpll_enable, ++ .disable = clk_hfpll_disable, ++ .is_enabled = hfpll_is_enabled, ++ .round_rate = clk_hfpll_round_rate, ++ .set_rate = clk_hfpll_set_rate, ++ .recalc_rate = clk_hfpll_recalc_rate, ++ .init = clk_hfpll_init, ++}; ++EXPORT_SYMBOL_GPL(clk_ops_hfpll); +--- /dev/null ++++ b/drivers/clk/qcom/clk-hfpll.h +@@ -0,0 +1,54 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++#ifndef __QCOM_CLK_HFPLL_H__ ++#define __QCOM_CLK_HFPLL_H__ ++ ++#include ++#include ++#include "clk-regmap.h" ++ ++struct hfpll_data { ++ u32 mode_reg; ++ u32 l_reg; ++ u32 m_reg; ++ u32 n_reg; ++ u32 user_reg; ++ u32 droop_reg; ++ u32 config_reg; ++ u32 status_reg; ++ u8 lock_bit; ++ ++ u32 droop_val; ++ u32 config_val; ++ u32 user_val; ++ u32 user_vco_mask; ++ unsigned long low_vco_max_rate; ++ ++ unsigned long min_rate; ++ unsigned long max_rate; ++}; ++ ++struct clk_hfpll { ++ struct hfpll_data const *d; ++ int init_done; ++ ++ struct clk_regmap clkr; ++ spinlock_t lock; ++}; ++ ++#define to_clk_hfpll(_hw) \ ++ container_of(to_clk_regmap(_hw), struct clk_hfpll, clkr) ++ ++extern const struct clk_ops clk_ops_hfpll; ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.1/138-clk-qcom-Add-HFPLL-driver.patch b/target/linux/ipq806x/patches-4.1/138-clk-qcom-Add-HFPLL-driver.patch new file mode 100644 index 0000000..4056784 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/138-clk-qcom-Add-HFPLL-driver.patch @@ -0,0 +1,206 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,06/13] clk: qcom: Add HFPLL driver +From: Stephen Boyd +X-Patchwork-Id: 6063231 +Message-Id: <1426920332-9340-7-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:25 -0700 + +On some devices (MSM8974 for example), the HFPLLs are +instantiated within the Krait processor subsystem as separate +register regions. Add a driver for these PLLs so that we can +provide HFPLL clocks for use by the system. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/clock/qcom,hfpll.txt | 40 ++++++++ + drivers/clk/qcom/Kconfig | 8 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/hfpll.c | 109 +++++++++++++++++++++ + 4 files changed, 158 insertions(+) + create mode 100644 Documentation/devicetree/bindings/clock/qcom,hfpll.txt + create mode 100644 drivers/clk/qcom/hfpll.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/clock/qcom,hfpll.txt +@@ -0,0 +1,40 @@ ++High-Frequency PLL (HFPLL) ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be "qcom,hfpll" ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: address and size of HPLL registers. An optional second ++ element specifies the address and size of the alias ++ register region. ++ ++- clock-output-names: ++ Usage: required ++ Value type: ++ Definition: Name of the PLL. Typically hfpllX where X is a CPU number ++ starting at 0. Otherwise hfpll_Y where Y is more specific ++ such as "l2". ++ ++Example: ++ ++1) An HFPLL for the L2 cache. ++ ++ clock-controller@f9016000 { ++ compatible = "qcom,hfpll"; ++ reg = <0xf9016000 0x30>; ++ clock-output-names = "hfpll_l2"; ++ }; ++ ++2) An HFPLL for CPU0. This HFPLL has the alias register region. ++ ++ clock-controller@f908a000 { ++ compatible = "qcom,hfpll"; ++ reg = <0xf908a000 0x30>, <0xf900a000 0x30>; ++ clock-output-names = "hfpll0"; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -97,3 +97,11 @@ config MSM_MMCC_8974 + Support for the multimedia clock controller on msm8974 devices. + Say Y if you want to support multimedia devices such as display, + graphics, video encode/decode, camera, etc. ++ ++config QCOM_HFPLL ++ tristate "High-Frequency PLL (HFPLL) Clock Controller" ++ depends on COMMON_CLK_QCOM ++ help ++ Support for the high-frequency PLLs present on Qualcomm devices. ++ Say Y if you want to support CPU frequency scaling on devices ++ such as MSM8974, APQ8084, etc. +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -22,3 +22,4 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm896 + obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o + obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o ++obj-$(CONFIG_QCOM_HFPLL) += hfpll.o +--- /dev/null ++++ b/drivers/clk/qcom/hfpll.c +@@ -0,0 +1,109 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clk-regmap.h" ++#include "clk-hfpll.h" ++ ++static const struct hfpll_data hdata = { ++ .mode_reg = 0x00, ++ .l_reg = 0x04, ++ .m_reg = 0x08, ++ .n_reg = 0x0c, ++ .user_reg = 0x10, ++ .config_reg = 0x14, ++ .config_val = 0x430405d, ++ .status_reg = 0x1c, ++ .lock_bit = 16, ++ ++ .user_val = 0x8, ++ .user_vco_mask = 0x100000, ++ .low_vco_max_rate = 1248000000, ++ .min_rate = 537600000UL, ++ .max_rate = 2900000000UL, ++}; ++ ++static const struct of_device_id qcom_hfpll_match_table[] = { ++ { .compatible = "qcom,hfpll" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(of, qcom_hfpll_match_table); ++ ++static const struct regmap_config hfpll_regmap_config = { ++ .reg_bits = 32, ++ .reg_stride = 4, ++ .val_bits = 32, ++ .max_register = 0x30, ++ .fast_io = true, ++}; ++ ++static int qcom_hfpll_probe(struct platform_device *pdev) ++{ ++ struct clk *clk; ++ struct resource *res; ++ struct device *dev = &pdev->dev; ++ void __iomem *base; ++ struct regmap *regmap; ++ struct clk_hfpll *h; ++ struct clk_init_data init = { ++ .parent_names = (const char *[]){ "xo" }, ++ .num_parents = 1, ++ .ops = &clk_ops_hfpll, ++ }; ++ ++ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL); ++ if (!h) ++ return -ENOMEM; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ regmap = devm_regmap_init_mmio(&pdev->dev, base, &hfpll_regmap_config); ++ if (IS_ERR(regmap)) ++ return PTR_ERR(regmap); ++ ++ if (of_property_read_string_index(dev->of_node, "clock-output-names", ++ 0, &init.name)) ++ return -ENODEV; ++ ++ h->d = &hdata; ++ h->clkr.hw.init = &init; ++ spin_lock_init(&h->lock); ++ ++ clk = devm_clk_register_regmap(&pdev->dev, &h->clkr); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static struct platform_driver qcom_hfpll_driver = { ++ .probe = qcom_hfpll_probe, ++ .driver = { ++ .name = "qcom-hfpll", ++ .of_match_table = qcom_hfpll_match_table, ++ }, ++}; ++module_platform_driver(qcom_hfpll_driver); ++ ++MODULE_DESCRIPTION("QCOM HFPLL Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:qcom-hfpll"); diff --git a/target/linux/ipq806x/patches-4.1/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch b/target/linux/ipq806x/patches-4.1/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch new file mode 100644 index 0000000..ffcd462 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/139-clk-qcom-Add-IPQ806X-s-HFPLLs.patch @@ -0,0 +1,127 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,08/13] clk: qcom: Add IPQ806X's HFPLLs +From: Stephen Boyd +X-Patchwork-Id: 6063241 +Message-Id: <1426920332-9340-9-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:27 -0700 + +Describe the HFPLLs present on IPQ806X devices. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/qcom/gcc-ipq806x.c | 83 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 83 insertions(+) + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -30,6 +30,7 @@ + #include "clk-pll.h" + #include "clk-rcg.h" + #include "clk-branch.h" ++#include "clk-hfpll.h" + #include "reset.h" + + static struct clk_pll pll0 = { +@@ -113,6 +114,85 @@ static struct clk_regmap pll8_vote = { + }, + }; + ++static struct hfpll_data hfpll0_data = { ++ .mode_reg = 0x3200, ++ .l_reg = 0x3208, ++ .m_reg = 0x320c, ++ .n_reg = 0x3210, ++ .config_reg = 0x3204, ++ .status_reg = 0x321c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3214, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll0 = { ++ .d = &hfpll0_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll0", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll0.lock), ++}; ++ ++static struct hfpll_data hfpll1_data = { ++ .mode_reg = 0x3240, ++ .l_reg = 0x3248, ++ .m_reg = 0x324c, ++ .n_reg = 0x3250, ++ .config_reg = 0x3244, ++ .status_reg = 0x325c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3314, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll1 = { ++ .d = &hfpll1_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll1", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll1.lock), ++}; ++ ++static struct hfpll_data hfpll_l2_data = { ++ .mode_reg = 0x3300, ++ .l_reg = 0x3308, ++ .m_reg = 0x330c, ++ .n_reg = 0x3310, ++ .config_reg = 0x3304, ++ .status_reg = 0x331c, ++ .config_val = 0x7845c665, ++ .droop_reg = 0x3314, ++ .droop_val = 0x0108c000, ++ .min_rate = 600000000UL, ++ .max_rate = 1800000000UL, ++}; ++ ++static struct clk_hfpll hfpll_l2 = { ++ .d = &hfpll_l2_data, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .name = "hfpll_l2", ++ .ops = &clk_ops_hfpll, ++ .flags = CLK_IGNORE_UNUSED, ++ }, ++ .lock = __SPIN_LOCK_UNLOCKED(hfpll_l2.lock), ++}; ++ ++ + static struct clk_pll pll14 = { + .l_reg = 0x31c4, + .m_reg = 0x31c8, +@@ -2307,6 +2387,9 @@ static struct clk_regmap *gcc_ipq806x_cl + [USB_FS1_SYSTEM_CLK] = &usb_fs1_sys_clk.clkr, + [EBI2_CLK] = &ebi2_clk.clkr, + [EBI2_AON_CLK] = &ebi2_aon_clk.clkr, ++ [PLL9] = &hfpll0.clkr, ++ [PLL10] = &hfpll1.clkr, ++ [PLL12] = &hfpll_l2.clkr, + }; + + static const struct qcom_reset_map gcc_ipq806x_resets[] = { diff --git a/target/linux/ipq806x/patches-4.1/140-clk-qcom-Add-support-for-Krait-clocks.patch b/target/linux/ipq806x/patches-4.1/140-clk-qcom-Add-support-for-Krait-clocks.patch new file mode 100644 index 0000000..5fcc787 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/140-clk-qcom-Add-support-for-Krait-clocks.patch @@ -0,0 +1,271 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,09/13] clk: qcom: Add support for Krait clocks +From: Stephen Boyd +X-Patchwork-Id: 6063251 +Message-Id: <1426920332-9340-10-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar +Date: Fri, 20 Mar 2015 23:45:28 -0700 + +The Krait clocks are made up of a series of muxes and a divider +that choose between a fixed rate clock and dedicated HFPLLs for +each CPU. Instead of using mmio accesses to remux parents, the +Krait implementation exposes the remux control via cp15 +registers. Support these clocks. + +Signed-off-by: Stephen Boyd + +--- +drivers/clk/qcom/Kconfig | 4 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/clk-krait.c | 166 +++++++++++++++++++++++++++++++++++++++++++ + drivers/clk/qcom/clk-krait.h | 49 +++++++++++++ + 4 files changed, 220 insertions(+) + create mode 100644 drivers/clk/qcom/clk-krait.c + create mode 100644 drivers/clk/qcom/clk-krait.h + +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -105,3 +105,7 @@ config QCOM_HFPLL + Support for the high-frequency PLLs present on Qualcomm devices. + Say Y if you want to support CPU frequency scaling on devices + such as MSM8974, APQ8084, etc. ++ ++config KRAIT_CLOCKS ++ bool ++ select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -8,6 +8,7 @@ clk-qcom-y += clk-rcg2.o + clk-qcom-y += clk-branch.o + clk-qcom-y += clk-regmap-divider.o + clk-qcom-y += clk-regmap-mux.o ++clk-qcom-$(CONFIG_KRAIT_CLOCKS) += clk-krait.o + clk-qcom-y += clk-hfpll.o + clk-qcom-y += reset.o + +--- /dev/null ++++ b/drivers/clk/qcom/clk-krait.c +@@ -0,0 +1,166 @@ ++/* ++ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "clk-krait.h" ++ ++/* Secondary and primary muxes share the same cp15 register */ ++static DEFINE_SPINLOCK(krait_clock_reg_lock); ++ ++#define LPL_SHIFT 8 ++static void __krait_mux_set_sel(struct krait_mux_clk *mux, int sel) ++{ ++ unsigned long flags; ++ u32 regval; ++ ++ spin_lock_irqsave(&krait_clock_reg_lock, flags); ++ regval = krait_get_l2_indirect_reg(mux->offset); ++ regval &= ~(mux->mask << mux->shift); ++ regval |= (sel & mux->mask) << mux->shift; ++ if (mux->lpl) { ++ regval &= ~(mux->mask << (mux->shift + LPL_SHIFT)); ++ regval |= (sel & mux->mask) << (mux->shift + LPL_SHIFT); ++ } ++ krait_set_l2_indirect_reg(mux->offset, regval); ++ spin_unlock_irqrestore(&krait_clock_reg_lock, flags); ++ ++ /* Wait for switch to complete. */ ++ mb(); ++ udelay(1); ++} ++ ++static int krait_mux_set_parent(struct clk_hw *hw, u8 index) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ u32 sel; ++ ++ sel = clk_mux_reindex(index, mux->parent_map, 0); ++ mux->en_mask = sel; ++ /* Don't touch mux if CPU is off as it won't work */ ++ if (__clk_is_enabled(hw->clk)) ++ __krait_mux_set_sel(mux, sel); ++ return 0; ++} ++ ++static u8 krait_mux_get_parent(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ u32 sel; ++ ++ sel = krait_get_l2_indirect_reg(mux->offset); ++ sel >>= mux->shift; ++ sel &= mux->mask; ++ mux->en_mask = sel; ++ ++ return clk_mux_get_parent(hw, sel, mux->parent_map, 0); ++} ++ ++static struct clk_hw *krait_mux_get_safe_parent(struct clk_hw *hw) ++{ ++ int i; ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ int num_parents = __clk_get_num_parents(hw->clk); ++ ++ i = mux->safe_sel; ++ for (i = 0; i < num_parents; i++) ++ if (mux->safe_sel == mux->parent_map[i]) ++ break; ++ ++ return __clk_get_hw(clk_get_parent_by_index(hw->clk, i)); ++} ++ ++static int krait_mux_enable(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ ++ __krait_mux_set_sel(mux, mux->en_mask); ++ ++ return 0; ++} ++ ++static void krait_mux_disable(struct clk_hw *hw) ++{ ++ struct krait_mux_clk *mux = to_krait_mux_clk(hw); ++ ++ __krait_mux_set_sel(mux, mux->safe_sel); ++} ++ ++const struct clk_ops krait_mux_clk_ops = { ++ .enable = krait_mux_enable, ++ .disable = krait_mux_disable, ++ .set_parent = krait_mux_set_parent, ++ .get_parent = krait_mux_get_parent, ++ .determine_rate = __clk_mux_determine_rate_closest, ++ .get_safe_parent = krait_mux_get_safe_parent, ++}; ++EXPORT_SYMBOL_GPL(krait_mux_clk_ops); ++ ++/* The divider can divide by 2, 4, 6 and 8. But we only really need div-2. */ ++static long krait_div2_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ *parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), rate * 2); ++ return DIV_ROUND_UP(*parent_rate, 2); ++} ++ ++static int krait_div2_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct krait_div2_clk *d = to_krait_div2_clk(hw); ++ unsigned long flags; ++ u32 val; ++ u32 mask = BIT(d->width) - 1; ++ ++ if (d->lpl) ++ mask = mask << (d->shift + LPL_SHIFT) | mask << d->shift; ++ ++ spin_lock_irqsave(&krait_clock_reg_lock, flags); ++ val = krait_get_l2_indirect_reg(d->offset); ++ val &= ~mask; ++ krait_set_l2_indirect_reg(d->offset, val); ++ spin_unlock_irqrestore(&krait_clock_reg_lock, flags); ++ ++ return 0; ++} ++ ++static unsigned long ++krait_div2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) ++{ ++ struct krait_div2_clk *d = to_krait_div2_clk(hw); ++ u32 mask = BIT(d->width) - 1; ++ u32 div; ++ ++ div = krait_get_l2_indirect_reg(d->offset); ++ div >>= d->shift; ++ div &= mask; ++ div = (div + 1) * 2; ++ ++ return DIV_ROUND_UP(parent_rate, div); ++} ++ ++const struct clk_ops krait_div2_clk_ops = { ++ .round_rate = krait_div2_round_rate, ++ .set_rate = krait_div2_set_rate, ++ .recalc_rate = krait_div2_recalc_rate, ++}; ++EXPORT_SYMBOL_GPL(krait_div2_clk_ops); +--- /dev/null ++++ b/drivers/clk/qcom/clk-krait.h +@@ -0,0 +1,49 @@ ++/* ++ * Copyright (c) 2013, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __QCOM_CLK_KRAIT_H ++#define __QCOM_CLK_KRAIT_H ++ ++#include ++ ++struct krait_mux_clk { ++ unsigned int *parent_map; ++ bool has_safe_parent; ++ u8 safe_sel; ++ u32 offset; ++ u32 mask; ++ u32 shift; ++ u32 en_mask; ++ bool lpl; ++ ++ struct clk_hw hw; ++}; ++ ++#define to_krait_mux_clk(_hw) container_of(_hw, struct krait_mux_clk, hw) ++ ++extern const struct clk_ops krait_mux_clk_ops; ++ ++struct krait_div2_clk { ++ u32 offset; ++ u8 width; ++ u32 shift; ++ bool lpl; ++ ++ struct clk_hw hw; ++}; ++ ++#define to_krait_div2_clk(_hw) container_of(_hw, struct krait_div2_clk, hw) ++ ++extern const struct clk_ops krait_div2_clk_ops; ++ ++#endif diff --git a/target/linux/ipq806x/patches-4.1/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch b/target/linux/ipq806x/patches-4.1/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch new file mode 100644 index 0000000..db43888 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/141-clk-qcom-Add-KPSS-ACC-GCC-driver.patch @@ -0,0 +1,205 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,10/13] clk: qcom: Add KPSS ACC/GCC driver +From: Stephen Boyd +X-Patchwork-Id: 6063201 +Message-Id: <1426920332-9340-11-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:29 -0700 + +The ACC and GCC regions present in KPSSv1 contain registers to +control clocks and power to each Krait CPU and L2. For CPUfreq +purposes probe these devices and expose a mux clock that chooses +between PXO and PLL8. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/arm/msm/qcom,kpss-acc.txt | 7 ++ + .../devicetree/bindings/arm/msm/qcom,kpss-gcc.txt | 28 +++++++ + drivers/clk/qcom/Kconfig | 8 ++ + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/kpss-xcc.c | 95 ++++++++++++++++++++++ + 5 files changed, 139 insertions(+) + create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt + create mode 100644 drivers/clk/qcom/kpss-xcc.c + +--- a/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-acc.txt +@@ -21,10 +21,17 @@ PROPERTIES + the register region. An optional second element specifies + the base address and size of the alias register region. + ++- clock-output-names: ++ Usage: optional ++ Value type: ++ Definition: Name of the output clock. Typically acpuX_aux where X is a ++ CPU number starting at 0. ++ + Example: + + clock-controller@2088000 { + compatible = "qcom,kpss-acc-v2"; + reg = <0x02088000 0x1000>, + <0x02008000 0x1000>; ++ clock-output-names = "acpu0_aux"; + }; +--- /dev/null ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,kpss-gcc.txt +@@ -0,0 +1,28 @@ ++Krait Processor Sub-system (KPSS) Global Clock Controller (GCC) ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: should be one of: ++ "qcom,kpss-gcc" ++ ++- reg: ++ Usage: required ++ Value type: ++ Definition: base address and size of the register region ++ ++- clock-output-names: ++ Usage: required ++ Value type: ++ Definition: Name of the output clock. Typically acpu_l2_aux indicating ++ an L2 cache auxiliary clock. ++ ++Example: ++ ++ l2cc: clock-controller@2011000 { ++ compatible = "qcom,kpss-gcc"; ++ reg = <0x2011000 0x1000>; ++ clock-output-names = "acpu_l2_aux"; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -106,6 +106,14 @@ config QCOM_HFPLL + Say Y if you want to support CPU frequency scaling on devices + such as MSM8974, APQ8084, etc. + ++config KPSS_XCC ++ tristate "KPSS Clock Controller" ++ depends on COMMON_CLK_QCOM ++ help ++ Support for the Krait ACC and GCC clock controllers. Say Y ++ if you want to support CPU frequency scaling on devices such ++ as MSM8960, APQ8064, etc. ++ + config KRAIT_CLOCKS + bool + select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -23,4 +23,5 @@ obj-$(CONFIG_MSM_LCC_8960) += lcc-msm896 + obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o + obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o ++obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o + obj-$(CONFIG_QCOM_HFPLL) += hfpll.o +--- /dev/null ++++ b/drivers/clk/qcom/kpss-xcc.c +@@ -0,0 +1,95 @@ ++/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static const char *aux_parents[] = { ++ "pll8_vote", ++ "pxo", ++}; ++ ++static unsigned int aux_parent_map[] = { ++ 3, ++ 0, ++}; ++ ++static const struct of_device_id kpss_xcc_match_table[] = { ++ { .compatible = "qcom,kpss-acc-v1", .data = (void *)1UL }, ++ { .compatible = "qcom,kpss-gcc" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, kpss_xcc_match_table); ++ ++static int kpss_xcc_driver_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *id; ++ struct clk *clk; ++ struct resource *res; ++ void __iomem *base; ++ const char *name; ++ ++ id = of_match_device(kpss_xcc_match_table, &pdev->dev); ++ if (!id) ++ return -ENODEV; ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(base)) ++ return PTR_ERR(base); ++ ++ if (id->data) { ++ if (of_property_read_string_index(pdev->dev.of_node, ++ "clock-output-names", 0, &name)) ++ return -ENODEV; ++ base += 0x14; ++ } else { ++ name = "acpu_l2_aux"; ++ base += 0x28; ++ } ++ ++ clk = clk_register_mux_table(&pdev->dev, name, aux_parents, ++ ARRAY_SIZE(aux_parents), 0, base, 0, 0x3, ++ 0, aux_parent_map, NULL); ++ ++ platform_set_drvdata(pdev, clk); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static int kpss_xcc_driver_remove(struct platform_device *pdev) ++{ ++ clk_unregister_mux(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static struct platform_driver kpss_xcc_driver = { ++ .probe = kpss_xcc_driver_probe, ++ .remove = kpss_xcc_driver_remove, ++ .driver = { ++ .name = "kpss-xcc", ++ .of_match_table = kpss_xcc_match_table, ++ }, ++}; ++module_platform_driver(kpss_xcc_driver); ++ ++MODULE_DESCRIPTION("Krait Processor Sub System (KPSS) Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:kpss-xcc"); diff --git a/target/linux/ipq806x/patches-4.1/142-clk-qcom-Add-Krait-clock-controller-driver.patch b/target/linux/ipq806x/patches-4.1/142-clk-qcom-Add-Krait-clock-controller-driver.patch new file mode 100644 index 0000000..59fc44f --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/142-clk-qcom-Add-Krait-clock-controller-driver.patch @@ -0,0 +1,435 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,11/13] clk: qcom: Add Krait clock controller driver +From: Stephen Boyd +X-Patchwork-Id: 6063121 +Message-Id: <1426920332-9340-12-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:30 -0700 + +The Krait CPU clocks are made up of a primary mux and secondary +mux for each CPU and the L2, controlled via cp15 accessors. For +Kraits within KPSSv1 each secondary mux accepts a different aux +source, but on KPSSv2 each secondary mux accepts the same aux +source. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/clock/qcom,krait-cc.txt | 22 ++ + drivers/clk/qcom/Kconfig | 8 + + drivers/clk/qcom/Makefile | 1 + + drivers/clk/qcom/krait-cc.c | 352 +++++++++++++++++++++ + 4 files changed, 383 insertions(+) + create mode 100644 Documentation/devicetree/bindings/clock/qcom,krait-cc.txt + create mode 100644 drivers/clk/qcom/krait-cc.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/clock/qcom,krait-cc.txt +@@ -0,0 +1,22 @@ ++Krait Clock Controller ++ ++PROPERTIES ++ ++- compatible: ++ Usage: required ++ Value type: ++ Definition: must be one of: ++ "qcom,krait-cc-v1" ++ "qcom,krait-cc-v2" ++ ++- #clock-cells: ++ Usage: required ++ Value type: ++ Definition: must be 1 ++ ++Example: ++ ++ kraitcc: clock-controller { ++ compatible = "qcom,krait-cc-v1"; ++ #clock-cells = <1>; ++ }; +--- a/drivers/clk/qcom/Kconfig ++++ b/drivers/clk/qcom/Kconfig +@@ -114,6 +114,14 @@ config KPSS_XCC + if you want to support CPU frequency scaling on devices such + as MSM8960, APQ8064, etc. + ++config KRAITCC ++ tristate "Krait Clock Controller" ++ depends on COMMON_CLK_QCOM && ARM ++ select KRAIT_CLOCKS ++ help ++ Support for the Krait CPU clocks on Qualcomm devices. ++ Say Y if you want to support CPU frequency scaling. ++ + config KRAIT_CLOCKS + bool + select KRAIT_L2_ACCESSORS +--- a/drivers/clk/qcom/Makefile ++++ b/drivers/clk/qcom/Makefile +@@ -25,3 +25,4 @@ obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8 + obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o + obj-$(CONFIG_KPSS_XCC) += kpss-xcc.o + obj-$(CONFIG_QCOM_HFPLL) += hfpll.o ++obj-$(CONFIG_KRAITCC) += krait-cc.o +--- /dev/null ++++ b/drivers/clk/qcom/krait-cc.c +@@ -0,0 +1,352 @@ ++/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "clk-krait.h" ++ ++static unsigned int sec_mux_map[] = { ++ 2, ++ 0, ++}; ++ ++static unsigned int pri_mux_map[] = { ++ 1, ++ 2, ++ 0, ++}; ++ ++static int ++krait_add_div(struct device *dev, int id, const char *s, unsigned offset) ++{ ++ struct krait_div2_clk *div; ++ struct clk_init_data init = { ++ .num_parents = 1, ++ .ops = &krait_div2_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ const char *p_names[1]; ++ struct clk *clk; ++ ++ div = devm_kzalloc(dev, sizeof(*div), GFP_KERNEL); ++ if (!div) ++ return -ENOMEM; ++ ++ div->width = 2; ++ div->shift = 6; ++ div->lpl = id >= 0; ++ div->offset = offset; ++ div->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "hfpll%s_div", s); ++ if (!init.name) ++ return -ENOMEM; ++ ++ init.parent_names = p_names; ++ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s); ++ if (!p_names[0]) { ++ kfree(init.name); ++ return -ENOMEM; ++ } ++ ++ clk = devm_clk_register(dev, &div->hw); ++ kfree(p_names[0]); ++ kfree(init.name); ++ ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static int ++krait_add_sec_mux(struct device *dev, int id, const char *s, unsigned offset, ++ bool unique_aux) ++{ ++ struct krait_mux_clk *mux; ++ static const char *sec_mux_list[] = { ++ "acpu_aux", ++ "qsb", ++ }; ++ struct clk_init_data init = { ++ .parent_names = sec_mux_list, ++ .num_parents = ARRAY_SIZE(sec_mux_list), ++ .ops = &krait_mux_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ struct clk *clk; ++ ++ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return -ENOMEM; ++ ++ mux->offset = offset; ++ mux->lpl = id >= 0; ++ mux->has_safe_parent = true; ++ mux->safe_sel = 2; ++ mux->mask = 0x3; ++ mux->shift = 2; ++ mux->parent_map = sec_mux_map; ++ mux->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); ++ if (!init.name) ++ return -ENOMEM; ++ ++ if (unique_aux) { ++ sec_mux_list[0] = kasprintf(GFP_KERNEL, "acpu%s_aux", s); ++ if (!sec_mux_list[0]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_aux; ++ } ++ } ++ ++ clk = devm_clk_register(dev, &mux->hw); ++ ++ if (unique_aux) ++ kfree(sec_mux_list[0]); ++err_aux: ++ kfree(init.name); ++ return PTR_ERR_OR_ZERO(clk); ++} ++ ++static struct clk * ++krait_add_pri_mux(struct device *dev, int id, const char *s, unsigned offset) ++{ ++ struct krait_mux_clk *mux; ++ const char *p_names[3]; ++ struct clk_init_data init = { ++ .parent_names = p_names, ++ .num_parents = ARRAY_SIZE(p_names), ++ .ops = &krait_mux_clk_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }; ++ struct clk *clk; ++ ++ mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); ++ if (!mux) ++ return ERR_PTR(-ENOMEM); ++ ++ mux->has_safe_parent = true; ++ mux->safe_sel = 0; ++ mux->mask = 0x3; ++ mux->shift = 0; ++ mux->offset = offset; ++ mux->lpl = id >= 0; ++ mux->parent_map = pri_mux_map; ++ mux->hw.init = &init; ++ ++ init.name = kasprintf(GFP_KERNEL, "krait%s_pri_mux", s); ++ if (!init.name) ++ return ERR_PTR(-ENOMEM); ++ ++ p_names[0] = kasprintf(GFP_KERNEL, "hfpll%s", s); ++ if (!p_names[0]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p0; ++ } ++ ++ p_names[1] = kasprintf(GFP_KERNEL, "hfpll%s_div", s); ++ if (!p_names[1]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p1; ++ } ++ ++ p_names[2] = kasprintf(GFP_KERNEL, "krait%s_sec_mux", s); ++ if (!p_names[2]) { ++ clk = ERR_PTR(-ENOMEM); ++ goto err_p2; ++ } ++ ++ clk = devm_clk_register(dev, &mux->hw); ++ ++ kfree(p_names[2]); ++err_p2: ++ kfree(p_names[1]); ++err_p1: ++ kfree(p_names[0]); ++err_p0: ++ kfree(init.name); ++ return clk; ++} ++ ++/* id < 0 for L2, otherwise id == physical CPU number */ ++static struct clk *krait_add_clks(struct device *dev, int id, bool unique_aux) ++{ ++ int ret; ++ unsigned offset; ++ void *p = NULL; ++ const char *s; ++ struct clk *clk; ++ ++ if (id >= 0) { ++ offset = 0x4501 + (0x1000 * id); ++ s = p = kasprintf(GFP_KERNEL, "%d", id); ++ if (!s) ++ return ERR_PTR(-ENOMEM); ++ } else { ++ offset = 0x500; ++ s = "_l2"; ++ } ++ ++ ret = krait_add_div(dev, id, s, offset); ++ if (ret) { ++ clk = ERR_PTR(ret); ++ goto err; ++ } ++ ++ ret = krait_add_sec_mux(dev, id, s, offset, unique_aux); ++ if (ret) { ++ clk = ERR_PTR(ret); ++ goto err; ++ } ++ ++ clk = krait_add_pri_mux(dev, id, s, offset); ++err: ++ kfree(p); ++ return clk; ++} ++ ++static struct clk *krait_of_get(struct of_phandle_args *clkspec, void *data) ++{ ++ unsigned int idx = clkspec->args[0]; ++ struct clk **clks = data; ++ ++ if (idx >= 5) { ++ pr_err("%s: invalid clock index %d\n", __func__, idx); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return clks[idx] ? : ERR_PTR(-ENODEV); ++} ++ ++static const struct of_device_id krait_cc_match_table[] = { ++ { .compatible = "qcom,krait-cc-v1", (void *)1UL }, ++ { .compatible = "qcom,krait-cc-v2" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, krait_cc_match_table); ++ ++static int krait_cc_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ const struct of_device_id *id; ++ unsigned long cur_rate, aux_rate; ++ int cpu; ++ struct clk *clk; ++ struct clk **clks; ++ struct clk *l2_pri_mux_clk; ++ ++ id = of_match_device(krait_cc_match_table, dev); ++ if (!id) ++ return -ENODEV; ++ ++ /* Rate is 1 because 0 causes problems for __clk_mux_determine_rate */ ++ clk = clk_register_fixed_rate(dev, "qsb", NULL, CLK_IS_ROOT, 1); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ ++ if (!id->data) { ++ clk = clk_register_fixed_factor(dev, "acpu_aux", ++ "gpll0_vote", 0, 1, 2); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ } ++ ++ /* Krait configurations have at most 4 CPUs and one L2 */ ++ clks = devm_kcalloc(dev, 5, sizeof(*clks), GFP_KERNEL); ++ if (!clks) ++ return -ENOMEM; ++ ++ for_each_possible_cpu(cpu) { ++ clk = krait_add_clks(dev, cpu, id->data); ++ if (IS_ERR(clk)) ++ return PTR_ERR(clk); ++ clks[cpu] = clk; ++ } ++ ++ l2_pri_mux_clk = krait_add_clks(dev, -1, id->data); ++ if (IS_ERR(l2_pri_mux_clk)) ++ return PTR_ERR(l2_pri_mux_clk); ++ clks[4] = l2_pri_mux_clk; ++ ++ /* ++ * We don't want the CPU or L2 clocks to be turned off at late init ++ * if CPUFREQ or HOTPLUG configs are disabled. So, bump up the ++ * refcount of these clocks. Any cpufreq/hotplug manager can assume ++ * that the clocks have already been prepared and enabled by the time ++ * they take over. ++ */ ++ for_each_online_cpu(cpu) { ++ clk_prepare_enable(l2_pri_mux_clk); ++ WARN(clk_prepare_enable(clks[cpu]), ++ "Unable to turn on CPU%d clock", cpu); ++ } ++ ++ /* ++ * Force reinit of HFPLLs and muxes to overwrite any potential ++ * incorrect configuration of HFPLLs and muxes by the bootloader. ++ * While at it, also make sure the cores are running at known rates ++ * and print the current rate. ++ * ++ * The clocks are set to aux clock rate first to make sure the ++ * secondary mux is not sourcing off of QSB. The rate is then set to ++ * two different rates to force a HFPLL reinit under all ++ * circumstances. ++ */ ++ cur_rate = clk_get_rate(l2_pri_mux_clk); ++ aux_rate = 384000000; ++ if (cur_rate == 1) { ++ pr_info("L2 @ QSB rate. Forcing new rate.\n"); ++ cur_rate = aux_rate; ++ } ++ clk_set_rate(l2_pri_mux_clk, aux_rate); ++ clk_set_rate(l2_pri_mux_clk, 2); ++ clk_set_rate(l2_pri_mux_clk, cur_rate); ++ pr_info("L2 @ %lu KHz\n", clk_get_rate(l2_pri_mux_clk) / 1000); ++ for_each_possible_cpu(cpu) { ++ clk = clks[cpu]; ++ cur_rate = clk_get_rate(clk); ++ if (cur_rate == 1) { ++ pr_info("CPU%d @ QSB rate. Forcing new rate.\n", cpu); ++ cur_rate = aux_rate; ++ } ++ clk_set_rate(clk, aux_rate); ++ clk_set_rate(clk, 2); ++ clk_set_rate(clk, cur_rate); ++ pr_info("CPU%d @ %lu KHz\n", cpu, clk_get_rate(clk) / 1000); ++ } ++ ++ of_clk_add_provider(dev->of_node, krait_of_get, clks); ++ ++ return 0; ++} ++ ++static struct platform_driver krait_cc_driver = { ++ .probe = krait_cc_probe, ++ .driver = { ++ .name = "krait-cc", ++ .of_match_table = krait_cc_match_table, ++ }, ++}; ++module_platform_driver(krait_cc_driver); ++ ++MODULE_DESCRIPTION("Krait CPU Clock Driver"); ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:krait-cc"); diff --git a/target/linux/ipq806x/patches-4.1/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch b/target/linux/ipq806x/patches-4.1/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch new file mode 100644 index 0000000..fd37c05 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/143-cpufreq-Add-module-to-register-cpufreq-on-Krait-CPUs.patch @@ -0,0 +1,304 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,12/13] cpufreq: Add module to register cpufreq on Krait CPUs +From: Stephen Boyd +X-Patchwork-Id: 6063191 +Message-Id: <1426920332-9340-13-git-send-email-sboyd@codeaurora.org> +To: Mike Turquette , Stephen Boyd +Cc: linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, + linux-pm@vger.kernel.org, linux-arm-kernel@lists.infradead.org, + Viresh Kumar , +Date: Fri, 20 Mar 2015 23:45:31 -0700 + +Register a cpufreq-generic device whenever we detect that a +"qcom,krait" compatible CPU is present in DT. + +Cc: +Signed-off-by: Stephen Boyd + +--- +.../devicetree/bindings/arm/msm/qcom,pvs.txt | 38 ++++ + drivers/cpufreq/Kconfig.arm | 9 + + drivers/cpufreq/Makefile | 1 + + drivers/cpufreq/qcom-cpufreq.c | 204 +++++++++++++++++++++ + 4 files changed, 252 insertions(+) + create mode 100644 Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt + create mode 100644 drivers/cpufreq/qcom-cpufreq.c + +--- /dev/null ++++ b/Documentation/devicetree/bindings/arm/msm/qcom,pvs.txt +@@ -0,0 +1,38 @@ ++Qualcomm Process Voltage Scaling Tables ++ ++The node name is required to be "qcom,pvs". There shall only be one ++such node present in the root of the tree. ++ ++PROPERTIES ++ ++- qcom,pvs-format-a or qcom,pvs-format-b: ++ Usage: required ++ Value type: ++ Definition: Indicates the format of qcom,speedX-pvsY-bin-vZ properties. ++ If qcom,pvs-format-a is used the table is two columns ++ (frequency and voltage in that order). If qcom,pvs-format-b is used the table is three columns (frequency, voltage, ++ and current in that order). ++ ++- qcom,speedX-pvsY-bin-vZ: ++ Usage: required ++ Value type: ++ Definition: The PVS table corresponding to the speed bin X, pvs bin Y, ++ and version Z. ++Example: ++ ++ qcom,pvs { ++ qcom,pvs-format-a; ++ qcom,speed0-pvs0-bin-v0 = ++ < 384000000 950000 >, ++ < 486000000 975000 >, ++ < 594000000 1000000 >, ++ < 702000000 1025000 >, ++ < 810000000 1075000 >, ++ < 918000000 1100000 >, ++ < 1026000000 1125000 >, ++ < 1134000000 1175000 >, ++ < 1242000000 1200000 >, ++ < 1350000000 1225000 >, ++ < 1458000000 1237500 >, ++ < 1512000000 1250000 >; ++ }; +--- a/drivers/cpufreq/Kconfig.arm ++++ b/drivers/cpufreq/Kconfig.arm +@@ -146,6 +146,15 @@ config ARM_OMAP2PLUS_CPUFREQ + depends on ARCH_OMAP2PLUS + default ARCH_OMAP2PLUS + ++config ARM_QCOM_CPUFREQ ++ tristate "Qualcomm based" ++ depends on ARCH_QCOM ++ select PM_OPP ++ help ++ This adds the CPUFreq driver for Qualcomm SoC based boards. ++ ++ If in doubt, say N. ++ + config ARM_S3C_CPUFREQ + bool + help +--- a/drivers/cpufreq/Makefile ++++ b/drivers/cpufreq/Makefile +@@ -66,6 +66,7 @@ obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += ki + obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o + obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o + obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o ++obj-$(CONFIG_ARM_QCOM_CPUFREQ) += qcom-cpufreq.o + obj-$(CONFIG_ARM_S3C24XX_CPUFREQ) += s3c24xx-cpufreq.o + obj-$(CONFIG_ARM_S3C24XX_CPUFREQ_DEBUGFS) += s3c24xx-cpufreq-debugfs.o + obj-$(CONFIG_ARM_S3C2410_CPUFREQ) += s3c2410-cpufreq.o +--- /dev/null ++++ b/drivers/cpufreq/qcom-cpufreq.c +@@ -0,0 +1,204 @@ ++/* Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static void __init get_krait_bin_format_a(int *speed, int *pvs, int *pvs_ver) ++{ ++ void __iomem *base; ++ u32 pte_efuse; ++ ++ *speed = *pvs = *pvs_ver = 0; ++ ++ base = ioremap(0x007000c0, 4); ++ if (!base) { ++ pr_warn("Unable to read efuse data. Defaulting to 0!\n"); ++ return; ++ } ++ ++ pte_efuse = readl_relaxed(base); ++ iounmap(base); ++ ++ *speed = pte_efuse & 0xf; ++ if (*speed == 0xf) ++ *speed = (pte_efuse >> 4) & 0xf; ++ ++ if (*speed == 0xf) { ++ *speed = 0; ++ pr_warn("Speed bin: Defaulting to %d\n", *speed); ++ } else { ++ pr_info("Speed bin: %d\n", *speed); ++ } ++ ++ *pvs = (pte_efuse >> 10) & 0x7; ++ if (*pvs == 0x7) ++ *pvs = (pte_efuse >> 13) & 0x7; ++ ++ if (*pvs == 0x7) { ++ *pvs = 0; ++ pr_warn("PVS bin: Defaulting to %d\n", *pvs); ++ } else { ++ pr_info("PVS bin: %d\n", *pvs); ++ } ++} ++ ++static void __init get_krait_bin_format_b(int *speed, int *pvs, int *pvs_ver) ++{ ++ u32 pte_efuse, redundant_sel; ++ void __iomem *base; ++ ++ *speed = 0; ++ *pvs = 0; ++ *pvs_ver = 0; ++ ++ base = ioremap(0xfc4b80b0, 8); ++ if (!base) { ++ pr_warn("Unable to read efuse data. Defaulting to 0!\n"); ++ return; ++ } ++ ++ pte_efuse = readl_relaxed(base); ++ redundant_sel = (pte_efuse >> 24) & 0x7; ++ *speed = pte_efuse & 0x7; ++ /* 4 bits of PVS are in efuse register bits 31, 8-6. */ ++ *pvs = ((pte_efuse >> 28) & 0x8) | ((pte_efuse >> 6) & 0x7); ++ *pvs_ver = (pte_efuse >> 4) & 0x3; ++ ++ switch (redundant_sel) { ++ case 1: ++ *speed = (pte_efuse >> 27) & 0xf; ++ break; ++ case 2: ++ *pvs = (pte_efuse >> 27) & 0xf; ++ break; ++ } ++ ++ /* Check SPEED_BIN_BLOW_STATUS */ ++ if (pte_efuse & BIT(3)) { ++ pr_info("Speed bin: %d\n", *speed); ++ } else { ++ pr_warn("Speed bin not set. Defaulting to 0!\n"); ++ *speed = 0; ++ } ++ ++ /* Check PVS_BLOW_STATUS */ ++ pte_efuse = readl_relaxed(base + 0x4) & BIT(21); ++ if (pte_efuse) { ++ pr_info("PVS bin: %d\n", *pvs); ++ } else { ++ pr_warn("PVS bin not set. Defaulting to 0!\n"); ++ *pvs = 0; ++ } ++ ++ pr_info("PVS version: %d\n", *pvs_ver); ++ iounmap(base); ++} ++ ++static int __init qcom_cpufreq_populate_opps(void) ++{ ++ int len, rows, cols, i, k, speed, pvs, pvs_ver; ++ char table_name[] = "qcom,speedXX-pvsXX-bin-vXX"; ++ struct device_node *np; ++ struct device *dev; ++ int cpu = 0; ++ ++ np = of_find_node_by_name(NULL, "qcom,pvs"); ++ if (!np) ++ return -ENODEV; ++ ++ if (of_property_read_bool(np, "qcom,pvs-format-a")) { ++ get_krait_bin_format_a(&speed, &pvs, &pvs_ver); ++ cols = 2; ++ } else if (of_property_read_bool(np, "qcom,pvs-format-b")) { ++ get_krait_bin_format_b(&speed, &pvs, &pvs_ver); ++ cols = 3; ++ } else { ++ return -ENODEV; ++ } ++ ++ snprintf(table_name, sizeof(table_name), ++ "qcom,speed%d-pvs%d-bin-v%d", speed, pvs, pvs_ver); ++ ++ if (!of_find_property(np, table_name, &len)) ++ return -EINVAL; ++ ++ len /= sizeof(u32); ++ if (len % cols || len == 0) ++ return -EINVAL; ++ ++ rows = len / cols; ++ ++ for (i = 0, k = 0; i < rows; i++) { ++ u32 freq, volt; ++ ++ of_property_read_u32_index(np, table_name, k++, &freq); ++ of_property_read_u32_index(np, table_name, k++, &volt); ++ while (k % cols) ++ k++; /* Skip uA entries if present */ ++ for (cpu = 0; cpu < num_possible_cpus(); cpu++) { ++ dev = get_cpu_device(cpu); ++ if (!dev) ++ return -ENODEV; ++ if (dev_pm_opp_add(dev, freq, volt)) ++ pr_warn("failed to add OPP %u\n", freq); ++ } ++ } ++ ++ return 0; ++} ++ ++static int __init qcom_cpufreq_driver_init(void) ++{ ++ struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; ++ struct platform_device_info devinfo = { ++ .name = "cpufreq-dt", ++ .data = &pdata, ++ .size_data = sizeof(pdata), ++ }; ++ struct device *cpu_dev; ++ struct device_node *np; ++ int ret; ++ ++ cpu_dev = get_cpu_device(0); ++ if (!cpu_dev) ++ return -ENODEV; ++ ++ np = of_node_get(cpu_dev->of_node); ++ if (!np) ++ return -ENOENT; ++ ++ if (!of_device_is_compatible(np, "qcom,krait")) { ++ of_node_put(np); ++ return -ENODEV; ++ } ++ of_node_put(np); ++ ++ ret = qcom_cpufreq_populate_opps(); ++ if (ret) ++ return ret; ++ ++ return PTR_ERR_OR_ZERO(platform_device_register_full(&devinfo)); ++} ++module_init(qcom_cpufreq_driver_init); ++ ++MODULE_DESCRIPTION("Qualcomm CPUfreq driver"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/ipq806x/patches-4.1/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch b/target/linux/ipq806x/patches-4.1/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch new file mode 100644 index 0000000..9435de6 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/144-ARM-dts-qcom-Add-necessary-DT-data-for-Krait-cpufreq.patch @@ -0,0 +1,100 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -25,6 +25,11 @@ + next-level-cache = <&L2>; + qcom,acc = <&acc0>; + qcom,saw = <&saw0>; ++ clocks = <&kraitcc 0>; ++ clock-names = "cpu"; ++ clock-latency = <100000>; ++ core-supply = <&smb208_s2a>; ++ voltage-tolerance = <5>; + }; + + cpu@1 { +@@ -35,11 +40,24 @@ + next-level-cache = <&L2>; + qcom,acc = <&acc1>; + qcom,saw = <&saw1>; ++ clocks = <&kraitcc 1>; ++ clock-names = "cpu"; ++ clock-latency = <100000>; ++ core-supply = <&smb208_s2b>; + }; + + L2: l2-cache { + compatible = "cache"; + cache-level = <2>; ++ clocks = <&kraitcc 4>; ++ clock-names = "cache"; ++ cache-points-kHz = < ++ /* kHz uV CPU kHz */ ++ 1200000 1150000 1200000 ++ 1000000 1100000 600000 ++ 384000 1100000 384000 ++ >; ++ vdd_dig-supply = <&smb208_s1a>; + }; + }; + +@@ -72,6 +90,46 @@ + }; + }; + ++ kraitcc: clock-controller { ++ compatible = "qcom,krait-cc-v1"; ++ #clock-cells = <1>; ++ }; ++ ++ qcom,pvs { ++ qcom,pvs-format-a; ++ qcom,speed0-pvs0-bin-v0 = ++ < 1400000000 1250000 >, ++ < 1200000000 1200000 >, ++ < 1000000000 1150000 >, ++ < 800000000 1100000 >, ++ < 600000000 1050000 >, ++ < 384000000 1000000 >; ++ ++ qcom,speed0-pvs1-bin-v0 = ++ < 1400000000 1175000 >, ++ < 1200000000 1125000 >, ++ < 1000000000 1075000 >, ++ < 800000000 1025000 >, ++ < 600000000 975000 >, ++ < 384000000 925000 >; ++ ++ qcom,speed0-pvs2-bin-v0 = ++ < 1400000000 1125000 >, ++ < 1200000000 1075000 >, ++ < 1000000000 1025000 >, ++ < 800000000 995000 >, ++ < 600000000 925000 >, ++ < 384000000 875000 >; ++ ++ qcom,speed0-pvs3-bin-v0 = ++ < 1400000000 1050000 >, ++ < 1200000000 1000000 >, ++ < 1000000000 950000 >, ++ < 800000000 900000 >, ++ < 600000000 850000 >, ++ < 384000000 800000 >; ++ }; ++ + soc: soc { + #address-cells = <1>; + #size-cells = <1>; +@@ -187,11 +245,13 @@ + acc0: clock-controller@2088000 { + compatible = "qcom,kpss-acc-v1"; + reg = <0x02088000 0x1000>, <0x02008000 0x1000>; ++ clock-output-names = "acpu0_aux"; + }; + + acc1: clock-controller@2098000 { + compatible = "qcom,kpss-acc-v1"; + reg = <0x02098000 0x1000>, <0x02008000 0x1000>; ++ clock-output-names = "acpu1_aux"; + }; + + l2cc: clock-controller@2011000 { diff --git a/target/linux/ipq806x/patches-4.1/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch b/target/linux/ipq806x/patches-4.1/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch new file mode 100644 index 0000000..84f856b --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/145-cpufreq-Add-a-cpufreq-krait-based-on-cpufre.patch @@ -0,0 +1,461 @@ +From dd77db4143290689d3a5e1ec61627233d0711b66 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd +Date: Fri, 30 May 2014 16:36:11 -0700 +Subject: [PATCH] FROMLIST: cpufreq: Add a cpufreq-krait based on cpufreq-cpu0 + +Krait processors have individual clocks for each CPU that can +scale independently from one another. cpufreq-cpu0 is fairly +close to this, but assumes that there is only one clock for all +CPUs. Add a driver to support the Krait configuration. + +TODO: Merge into cpufreq-cpu0? Or make generic? + +Signed-off-by: Stephen Boyd + +--- + drivers/cpufreq/Kconfig | 13 +++ + drivers/cpufreq/Makefile | 1 + + drivers/cpufreq/cpufreq-krait.c | 190 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 204 insertions(+) + create mode 100644 drivers/cpufreq/cpufreq-krait.c + +--- a/drivers/cpufreq/Kconfig ++++ b/drivers/cpufreq/Kconfig +@@ -198,6 +198,19 @@ config CPUFREQ_DT + + If in doubt, say N. + ++config GENERIC_CPUFREQ_KRAIT ++ tristate "Krait cpufreq driver" ++ depends on HAVE_CLK && OF ++ # if CPU_THERMAL is on and THERMAL=m, CPU0 cannot be =y: ++ depends on !CPU_THERMAL || THERMAL ++ select PM_OPP ++ help ++ This adds a generic cpufreq driver for CPU0 frequency management. ++ It supports both uniprocessor (UP) and symmetric multiprocessor (SMP) ++ systems which share clock and voltage across all CPUs. ++ ++ If in doubt, say N. ++ + if X86 + source "drivers/cpufreq/Kconfig.x86" + endif +--- a/drivers/cpufreq/Makefile ++++ b/drivers/cpufreq/Makefile +@@ -14,6 +14,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) + obj-$(CONFIG_CPU_FREQ_GOV_COMMON) += cpufreq_governor.o + + obj-$(CONFIG_CPUFREQ_DT) += cpufreq-dt.o ++obj-$(CONFIG_GENERIC_CPUFREQ_KRAIT) += cpufreq-krait.o + + ################################################################################## + # x86 drivers. +--- /dev/null ++++ b/drivers/cpufreq/cpufreq-krait.c +@@ -0,0 +1,390 @@ ++/* ++ * Copyright (C) 2012 Freescale Semiconductor, Inc. ++ * Copyright (c) 2014, The Linux Foundation. All rights reserved. ++ * ++ * The OPP code in function krait_set_target() is reused from ++ * drivers/cpufreq/omap-cpufreq.c ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static unsigned int transition_latency; ++static unsigned int voltage_tolerance; /* in percentage */ ++ ++static struct device *cpu_dev; ++static DEFINE_PER_CPU(struct clk *, krait_cpu_clks); ++static DEFINE_PER_CPU(struct regulator *, krait_supply_core); ++static struct cpufreq_frequency_table *freq_table; ++static struct thermal_cooling_device *cdev; ++ ++struct cache_points { ++ unsigned long cache_freq; ++ unsigned int cache_volt; ++ unsigned long cpu_freq; ++}; ++ ++static struct regulator *krait_l2_reg; ++static struct clk *krait_l2_clk; ++static struct cache_points *krait_l2_points; ++static int nr_krait_l2_points; ++ ++static int krait_parse_cache_points(struct device *dev, ++ struct device_node *of_node) ++{ ++ const struct property *prop; ++ const __be32 *val; ++ int nr, i; ++ ++ prop = of_find_property(of_node, "cache-points-kHz", NULL); ++ if (!prop) ++ return -ENODEV; ++ if (!prop->value) ++ return -ENODATA; ++ ++ /* ++ * Each OPP is a set of tuples consisting of frequency and ++ * cpu-frequency like . ++ */ ++ nr = prop->length / sizeof(u32); ++ if (nr % 3) { ++ dev_err(dev, "%s: Invalid cache points\n", __func__); ++ return -EINVAL; ++ } ++ nr /= 3; ++ ++ krait_l2_points = devm_kcalloc(dev, nr, sizeof(*krait_l2_points), ++ GFP_KERNEL); ++ if (!krait_l2_points) ++ return -ENOMEM; ++ nr_krait_l2_points = nr; ++ ++ for (i = 0, val = prop->value; i < nr; i++) { ++ unsigned long cache_freq = be32_to_cpup(val++) * 1000; ++ unsigned int cache_volt = be32_to_cpup(val++); ++ unsigned long cpu_freq = be32_to_cpup(val++) * 1000; ++ ++ krait_l2_points[i].cache_freq = cache_freq; ++ krait_l2_points[i].cache_volt = cache_volt; ++ krait_l2_points[i].cpu_freq = cpu_freq; ++ } ++ ++ return 0; ++} ++ ++static int krait_set_target(struct cpufreq_policy *policy, unsigned int index) ++{ ++ struct dev_pm_opp *opp; ++ unsigned long volt = 0, volt_old = 0, tol = 0; ++ unsigned long freq, max_cpu_freq = 0; ++ unsigned int old_freq, new_freq; ++ long freq_Hz, freq_exact; ++ int ret, i; ++ struct clk *cpu_clk; ++ struct regulator *core; ++ unsigned int cpu; ++ ++ cpu_clk = per_cpu(krait_cpu_clks, policy->cpu); ++ ++ freq_Hz = clk_round_rate(cpu_clk, freq_table[index].frequency * 1000); ++ if (freq_Hz <= 0) ++ freq_Hz = freq_table[index].frequency * 1000; ++ ++ freq_exact = freq_Hz; ++ new_freq = freq_Hz / 1000; ++ old_freq = clk_get_rate(cpu_clk) / 1000; ++ ++ core = per_cpu(krait_supply_core, policy->cpu); ++ ++ rcu_read_lock(); ++ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz); ++ if (IS_ERR(opp)) { ++ rcu_read_unlock(); ++ pr_err("failed to find OPP for %ld\n", freq_Hz); ++ return PTR_ERR(opp); ++ } ++ volt = dev_pm_opp_get_voltage(opp); ++ rcu_read_unlock(); ++ tol = volt * voltage_tolerance / 100; ++ volt_old = regulator_get_voltage(core); ++ ++ pr_debug("%u MHz, %ld mV --> %u MHz, %ld mV\n", ++ old_freq / 1000, volt_old ? volt_old / 1000 : -1, ++ new_freq / 1000, volt ? volt / 1000 : -1); ++ ++ /* scaling up? scale voltage before frequency */ ++ if (new_freq > old_freq) { ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage up: %d\n", ret); ++ return ret; ++ } ++ } ++ ++ ret = clk_set_rate(cpu_clk, freq_exact); ++ if (ret) { ++ pr_err("failed to set clock rate: %d\n", ret); ++ return ret; ++ } ++ ++ /* scaling down? scale voltage after frequency */ ++ if (new_freq < old_freq) { ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage down: %d\n", ret); ++ clk_set_rate(cpu_clk, old_freq * 1000); ++ } ++ } ++ ++ for_each_possible_cpu(cpu) { ++ freq = clk_get_rate(per_cpu(krait_cpu_clks, cpu)); ++ max_cpu_freq = max(max_cpu_freq, freq); ++ } ++ ++ for (i = 0; i < nr_krait_l2_points; i++) { ++ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) { ++ if (krait_l2_reg) { ++ ret = regulator_set_voltage_tol(krait_l2_reg, ++ krait_l2_points[i].cache_volt, ++ tol); ++ if (ret) { ++ pr_err("failed to scale l2 voltage: %d\n", ++ ret); ++ } ++ } ++ ret = clk_set_rate(krait_l2_clk, ++ krait_l2_points[i].cache_freq); ++ if (ret) ++ pr_err("failed to scale l2 clk: %d\n", ret); ++ break; ++ } ++ ++ } ++ ++ return ret; ++} ++ ++static int krait_cpufreq_init(struct cpufreq_policy *policy) ++{ ++ int ret; ++ ++ policy->clk = per_cpu(krait_cpu_clks, policy->cpu); ++ ++ ret = cpufreq_table_validate_and_show(policy, freq_table); ++ if (ret) { ++ pr_err("%s: invalid frequency table: %d\n", __func__, ret); ++ return ret; ++ } ++ ++ policy->cpuinfo.transition_latency = transition_latency; ++ ++ return 0; ++} ++ ++static struct cpufreq_driver krait_cpufreq_driver = { ++ .flags = CPUFREQ_STICKY, ++ .verify = cpufreq_generic_frequency_table_verify, ++ .target_index = krait_set_target, ++ .get = cpufreq_generic_get, ++ .init = krait_cpufreq_init, ++ .name = "generic_krait", ++ .attr = cpufreq_generic_attr, ++}; ++ ++static int krait_cpufreq_probe(struct platform_device *pdev) ++{ ++ struct device_node *np, *cache; ++ int ret, i; ++ unsigned int cpu; ++ struct device *dev; ++ struct clk *clk; ++ struct regulator *core; ++ unsigned long freq_Hz, freq, max_cpu_freq = 0; ++ struct dev_pm_opp *opp; ++ unsigned long volt, tol; ++ ++ cpu_dev = get_cpu_device(0); ++ if (!cpu_dev) { ++ pr_err("failed to get krait device\n"); ++ return -ENODEV; ++ } ++ ++ np = of_node_get(cpu_dev->of_node); ++ if (!np) { ++ pr_err("failed to find krait node\n"); ++ return -ENOENT; ++ } ++ ++ ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table); ++ if (ret) { ++ pr_err("failed to init cpufreq table: %d\n", ret); ++ goto out_put_node; ++ } ++ ++ of_property_read_u32(np, "voltage-tolerance", &voltage_tolerance); ++ ++ if (of_property_read_u32(np, "clock-latency", &transition_latency)) ++ transition_latency = CPUFREQ_ETERNAL; ++ ++ cache = of_find_next_cache_node(np); ++ if (cache) { ++ struct device_node *vdd; ++ ++ vdd = of_parse_phandle(cache, "vdd_dig-supply", 0); ++ if (vdd) { ++ krait_l2_reg = regulator_get(NULL, vdd->name); ++ if (IS_ERR(krait_l2_reg)) { ++ pr_warn("failed to get l2 vdd_dig supply\n"); ++ krait_l2_reg = NULL; ++ } ++ of_node_put(vdd); ++ } ++ ++ krait_l2_clk = of_clk_get(cache, 0); ++ if (!IS_ERR(krait_l2_clk)) { ++ ret = krait_parse_cache_points(&pdev->dev, cache); ++ if (ret) ++ clk_put(krait_l2_clk); ++ } ++ if (IS_ERR(krait_l2_clk) || ret) ++ krait_l2_clk = NULL; ++ } ++ ++ for_each_possible_cpu(cpu) { ++ dev = get_cpu_device(cpu); ++ if (!dev) { ++ pr_err("failed to get krait device\n"); ++ ret = -ENOENT; ++ goto out_free_table; ++ } ++ per_cpu(krait_cpu_clks, cpu) = clk = devm_clk_get(dev, NULL); ++ if (IS_ERR(clk)) { ++ ret = PTR_ERR(clk); ++ goto out_free_table; ++ } ++ core = devm_regulator_get(dev, "core"); ++ if (IS_ERR(core)) { ++ pr_debug("failed to get core regulator\n"); ++ ret = PTR_ERR(core); ++ goto out_free_table; ++ } ++ per_cpu(krait_supply_core, cpu) = core; ++ ++ freq = freq_Hz = clk_get_rate(clk); ++ ++ rcu_read_lock(); ++ opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_Hz); ++ if (IS_ERR(opp)) { ++ rcu_read_unlock(); ++ pr_err("failed to find OPP for %ld\n", freq_Hz); ++ ret = PTR_ERR(opp); ++ goto out_free_table; ++ } ++ volt = dev_pm_opp_get_voltage(opp); ++ rcu_read_unlock(); ++ ++ tol = volt * voltage_tolerance / 100; ++ ret = regulator_set_voltage_tol(core, volt, tol); ++ if (ret) { ++ pr_err("failed to scale voltage up: %d\n", ret); ++ goto out_free_table; ++ } ++ ret = regulator_enable(core); ++ if (ret) { ++ pr_err("failed to enable regulator: %d\n", ret); ++ goto out_free_table; ++ } ++ max_cpu_freq = max(max_cpu_freq, freq); ++ } ++ ++ for (i = 0; i < nr_krait_l2_points; i++) { ++ if (max_cpu_freq >= krait_l2_points[i].cpu_freq) { ++ if (krait_l2_reg) { ++ ret = regulator_set_voltage_tol(krait_l2_reg, ++ krait_l2_points[i].cache_volt, ++ tol); ++ if (ret) ++ pr_err("failed to scale l2 voltage: %d\n", ++ ret); ++ ret = regulator_enable(krait_l2_reg); ++ if (ret) ++ pr_err("failed to enable l2 voltage: %d\n", ++ ret); ++ } ++ break; ++ } ++ ++ } ++ ++ ret = cpufreq_register_driver(&krait_cpufreq_driver); ++ if (ret) { ++ pr_err("failed register driver: %d\n", ret); ++ goto out_free_table; ++ } ++ of_node_put(np); ++ ++ /* ++ * For now, just loading the cooling device; ++ * thermal DT code takes care of matching them. ++ */ ++ for_each_possible_cpu(cpu) { ++ dev = get_cpu_device(cpu); ++ np = of_node_get(dev->of_node); ++ if (of_find_property(np, "#cooling-cells", NULL)) { ++ cdev = of_cpufreq_cooling_register(np, cpumask_of(cpu)); ++ if (IS_ERR(cdev)) ++ pr_err("running cpufreq without cooling device: %ld\n", ++ PTR_ERR(cdev)); ++ } ++ of_node_put(np); ++ } ++ ++ return 0; ++ ++out_free_table: ++ regulator_put(krait_l2_reg); ++ clk_put(krait_l2_clk); ++ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); ++out_put_node: ++ of_node_put(np); ++ return ret; ++} ++ ++static int krait_cpufreq_remove(struct platform_device *pdev) ++{ ++ cpufreq_cooling_unregister(cdev); ++ cpufreq_unregister_driver(&krait_cpufreq_driver); ++ dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table); ++ clk_put(krait_l2_clk); ++ regulator_put(krait_l2_reg); ++ ++ return 0; ++} ++ ++static struct platform_driver krait_cpufreq_platdrv = { ++ .driver = { ++ .name = "cpufreq-krait", ++ .owner = THIS_MODULE, ++ }, ++ .probe = krait_cpufreq_probe, ++ .remove = krait_cpufreq_remove, ++}; ++module_platform_driver(krait_cpufreq_platdrv); ++ ++MODULE_DESCRIPTION("Krait CPUfreq driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/cpufreq/qcom-cpufreq.c ++++ b/drivers/cpufreq/qcom-cpufreq.c +@@ -168,11 +168,8 @@ static int __init qcom_cpufreq_populate_ + + static int __init qcom_cpufreq_driver_init(void) + { +- struct cpufreq_dt_platform_data pdata = { .independent_clocks = true }; + struct platform_device_info devinfo = { +- .name = "cpufreq-dt", +- .data = &pdata, +- .size_data = sizeof(pdata), ++ .name = "cpufreq-krait", + }; + struct device *cpu_dev; + struct device_node *np; diff --git a/target/linux/ipq806x/patches-4.1/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch b/target/linux/ipq806x/patches-4.1/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch new file mode 100644 index 0000000..4f5c0ef --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/155-dt-bindings-qcom_adm-Fix-channel-specifiers.patch @@ -0,0 +1,76 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v6,1/2] dt/bindings: qcom_adm: Fix channel specifiers +From: Andy Gross +X-Patchwork-Id: 6027361 +Message-Id: <1426571172-9711-2-git-send-email-agross@codeaurora.org> +To: Vinod Koul +Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org, + linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, Kumar Gala , + Bjorn Andersson , + Andy Gross +Date: Tue, 17 Mar 2015 00:46:11 -0500 + +This patch removes the crci information from the dma channel property. At least +one client device requires using more than one CRCI value for a channel. This +does not match the current binding and the crci information needs to be removed. + +Instead, the client device will provide this information via other means. + +Signed-off-by: Andy Gross + +--- +Documentation/devicetree/bindings/dma/qcom_adm.txt | 16 ++++++---------- + 1 file changed, 6 insertions(+), 10 deletions(-) + +--- a/Documentation/devicetree/bindings/dma/qcom_adm.txt ++++ b/Documentation/devicetree/bindings/dma/qcom_adm.txt +@@ -4,8 +4,7 @@ Required properties: + - compatible: must contain "qcom,adm" for IPQ/APQ8064 and MSM8960 + - reg: Address range for DMA registers + - interrupts: Should contain one interrupt shared by all channels +-- #dma-cells: must be <2>. First cell denotes the channel number. Second cell +- denotes CRCI (client rate control interface) flow control assignment. ++- #dma-cells: must be <1>. First cell denotes the channel number. + - clocks: Should contain the core clock and interface clock. + - clock-names: Must contain "core" for the core clock and "iface" for the + interface clock. +@@ -22,7 +21,7 @@ Example: + compatible = "qcom,adm"; + reg = <0x18300000 0x100000>; + interrupts = <0 170 0>; +- #dma-cells = <2>; ++ #dma-cells = <1>; + + clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>; + clock-names = "core", "iface"; +@@ -35,15 +34,12 @@ Example: + qcom,ee = <0>; + }; + +-DMA clients must use the format descripted in the dma.txt file, using a three ++DMA clients must use the format descripted in the dma.txt file, using a two + cell specifier for each channel. + +-Each dmas request consists of 3 cells: ++Each dmas request consists of two cells: + 1. phandle pointing to the DMA controller + 2. channel number +- 3. CRCI assignment, if applicable. If no CRCI flow control is required, use 0. +- The CRCI is used for flow control. It identifies the peripheral device that +- is the source/destination for the transferred data. + + Example: + +@@ -56,7 +52,7 @@ Example: + + cs-gpios = <&qcom_pinmux 20 0>; + +- dmas = <&adm_dma 6 9>, +- <&adm_dma 5 10>; ++ dmas = <&adm_dma 6>, ++ <&adm_dma 5>; + dma-names = "rx", "tx"; + }; diff --git a/target/linux/ipq806x/patches-4.1/156-dmaengine-Add-ADM-driver.patch b/target/linux/ipq806x/patches-4.1/156-dmaengine-Add-ADM-driver.patch new file mode 100644 index 0000000..29e8eaa --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/156-dmaengine-Add-ADM-driver.patch @@ -0,0 +1,962 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v6,2/2] dmaengine: Add ADM driver +From: Andy Gross +X-Patchwork-Id: 6027351 +Message-Id: <1426571172-9711-3-git-send-email-agross@codeaurora.org> +To: Vinod Koul +Cc: devicetree@vger.kernel.org, dmaengine@vger.kernel.org, + linux-arm-msm@vger.kernel.org, linux-kernel@vger.kernel.org, + linux-arm-kernel@lists.infradead.org, Kumar Gala , + Bjorn Andersson , + Andy Gross +Date: Tue, 17 Mar 2015 00:46:12 -0500 + +Add the DMA engine driver for the QCOM Application Data Mover (ADM) DMA +controller found in the MSM8x60 and IPQ/APQ8064 platforms. + +The ADM supports both memory to memory transactions and memory +to/from peripheral device transactions. The controller also provides flow +control capabilities for transactions to/from peripheral devices. + +The initial release of this driver supports slave transfers to/from peripherals +and also incorporates CRCI (client rate control interface) flow control. + +Signed-off-by: Andy Gross +Reviewed-by: sricharan + +--- +drivers/dma/Kconfig | 10 + + drivers/dma/Makefile | 1 + + drivers/dma/qcom_adm.c | 900 ++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 911 insertions(+) + create mode 100644 drivers/dma/qcom_adm.c + +--- a/drivers/dma/Kconfig ++++ b/drivers/dma/Kconfig +@@ -492,4 +492,14 @@ config QCOM_BAM_DMA + Enable support for the QCOM BAM DMA controller. This controller + provides DMA capabilities for a variety of on-chip devices. + ++config QCOM_ADM ++ tristate "Qualcomm ADM support" ++ depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) ++ select DMA_ENGINE ++ select DMA_VIRTUAL_CHANNELS ++ ---help--- ++ Enable support for the Qualcomm ADM DMA controller. This controller ++ provides DMA capabilities for both general purpose and on-chip ++ peripheral devices. ++ + endif +--- /dev/null ++++ b/drivers/dma/qcom_adm.c +@@ -0,0 +1,900 @@ ++/* ++ * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 and ++ * only version 2 as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "dmaengine.h" ++#include "virt-dma.h" ++ ++/* ADM registers - calculated from channel number and security domain */ ++#define ADM_CHAN_MULTI 0x4 ++#define ADM_CI_MULTI 0x4 ++#define ADM_CRCI_MULTI 0x4 ++#define ADM_EE_MULTI 0x800 ++#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan) ++#define ADM_EE_OFFS(ee) (ADM_EE_MULTI * ee) ++#define ADM_CHAN_EE_OFFS(chan, ee) (ADM_CHAN_OFFS(chan) + ADM_EE_OFFS(ee)) ++#define ADM_CHAN_OFFS(chan) (ADM_CHAN_MULTI * chan) ++#define ADM_CI_OFFS(ci) (ADM_CHAN_OFF(ci)) ++#define ADM_CH_CMD_PTR(chan, ee) (ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_RSLT(chan, ee) (0x40 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_FLUSH_STATE0(chan, ee) (0x80 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_STATUS_SD(chan, ee) (0x200 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_CH_CONF(chan) (0x240 + ADM_CHAN_OFFS(chan)) ++#define ADM_CH_RSLT_CONF(chan, ee) (0x300 + ADM_CHAN_EE_OFFS(chan, ee)) ++#define ADM_SEC_DOMAIN_IRQ_STATUS(ee) (0x380 + ADM_EE_OFFS(ee)) ++#define ADM_CI_CONF(ci) (0x390 + ci * ADM_CI_MULTI) ++#define ADM_GP_CTL 0x3d8 ++#define ADM_CRCI_CTL(crci, ee) (0x400 + crci * ADM_CRCI_MULTI + \ ++ ADM_EE_OFFS(ee)) ++ ++/* channel status */ ++#define ADM_CH_STATUS_VALID BIT(1) ++ ++/* channel result */ ++#define ADM_CH_RSLT_VALID BIT(31) ++#define ADM_CH_RSLT_ERR BIT(3) ++#define ADM_CH_RSLT_FLUSH BIT(2) ++#define ADM_CH_RSLT_TPD BIT(1) ++ ++/* channel conf */ ++#define ADM_CH_CONF_SHADOW_EN BIT(12) ++#define ADM_CH_CONF_MPU_DISABLE BIT(11) ++#define ADM_CH_CONF_PERM_MPU_CONF BIT(9) ++#define ADM_CH_CONF_FORCE_RSLT_EN BIT(7) ++#define ADM_CH_CONF_SEC_DOMAIN(ee) (((ee & 0x3) << 4) | ((ee & 0x4) << 11)) ++ ++/* channel result conf */ ++#define ADM_CH_RSLT_CONF_FLUSH_EN BIT(1) ++#define ADM_CH_RSLT_CONF_IRQ_EN BIT(0) ++ ++/* CRCI CTL */ ++#define ADM_CRCI_CTL_MUX_SEL BIT(18) ++#define ADM_CRCI_CTL_RST BIT(17) ++ ++/* CI configuration */ ++#define ADM_CI_RANGE_END(x) (x << 24) ++#define ADM_CI_RANGE_START(x) (x << 16) ++#define ADM_CI_BURST_4_WORDS BIT(2) ++#define ADM_CI_BURST_8_WORDS BIT(3) ++ ++/* GP CTL */ ++#define ADM_GP_CTL_LP_EN BIT(12) ++#define ADM_GP_CTL_LP_CNT(x) (x << 8) ++ ++/* Command pointer list entry */ ++#define ADM_CPLE_LP BIT(31) ++#define ADM_CPLE_CMD_PTR_LIST BIT(29) ++ ++/* Command list entry */ ++#define ADM_CMD_LC BIT(31) ++#define ADM_CMD_DST_CRCI(n) (((n) & 0xf) << 7) ++#define ADM_CMD_SRC_CRCI(n) (((n) & 0xf) << 3) ++ ++#define ADM_CMD_TYPE_SINGLE 0x0 ++#define ADM_CMD_TYPE_BOX 0x3 ++ ++#define ADM_CRCI_MUX_SEL BIT(4) ++#define ADM_DESC_ALIGN 8 ++#define ADM_MAX_XFER (SZ_64K-1) ++#define ADM_MAX_ROWS (SZ_64K-1) ++#define ADM_MAX_CHANNELS 16 ++ ++struct adm_desc_hw_box { ++ u32 cmd; ++ u32 src_addr; ++ u32 dst_addr; ++ u32 row_len; ++ u32 num_rows; ++ u32 row_offset; ++}; ++ ++struct adm_desc_hw_single { ++ u32 cmd; ++ u32 src_addr; ++ u32 dst_addr; ++ u32 len; ++}; ++ ++struct adm_async_desc { ++ struct virt_dma_desc vd; ++ struct adm_device *adev; ++ ++ size_t length; ++ enum dma_transfer_direction dir; ++ dma_addr_t dma_addr; ++ size_t dma_len; ++ ++ void *cpl; ++ dma_addr_t cp_addr; ++ u32 crci; ++ u32 mux; ++ u32 blk_size; ++}; ++ ++struct adm_chan { ++ struct virt_dma_chan vc; ++ struct adm_device *adev; ++ ++ /* parsed from DT */ ++ u32 id; /* channel id */ ++ ++ struct adm_async_desc *curr_txd; ++ struct dma_slave_config slave; ++ struct list_head node; ++ ++ int error; ++ int initialized; ++}; ++ ++static inline struct adm_chan *to_adm_chan(struct dma_chan *common) ++{ ++ return container_of(common, struct adm_chan, vc.chan); ++} ++ ++struct adm_device { ++ void __iomem *regs; ++ struct device *dev; ++ struct dma_device common; ++ struct device_dma_parameters dma_parms; ++ struct adm_chan *channels; ++ ++ u32 ee; ++ ++ struct clk *core_clk; ++ struct clk *iface_clk; ++ ++ struct reset_control *clk_reset; ++ struct reset_control *c0_reset; ++ struct reset_control *c1_reset; ++ struct reset_control *c2_reset; ++ int irq; ++}; ++ ++/** ++ * adm_free_chan - Frees dma resources associated with the specific channel ++ * ++ * Free all allocated descriptors associated with this channel ++ * ++ */ ++static void adm_free_chan(struct dma_chan *chan) ++{ ++ /* free all queued descriptors */ ++ vchan_free_chan_resources(to_virt_chan(chan)); ++} ++ ++/** ++ * adm_get_blksize - Get block size from burst value ++ * ++ */ ++static int adm_get_blksize(unsigned int burst) ++{ ++ int ret; ++ ++ switch (burst) { ++ case 16: ++ case 32: ++ case 64: ++ case 128: ++ ret = ffs(burst>>4) - 1; ++ break; ++ case 192: ++ ret = 4; ++ break; ++ case 256: ++ ret = 5; ++ break; ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ return ret; ++} ++ ++/** ++ * adm_process_fc_descriptors - Process descriptors for flow controlled xfers ++ * ++ * @achan: ADM channel ++ * @desc: Descriptor memory pointer ++ * @sg: Scatterlist entry ++ * @crci: CRCI value ++ * @burst: Burst size of transaction ++ * @direction: DMA transfer direction ++ */ ++static void *adm_process_fc_descriptors(struct adm_chan *achan, ++ void *desc, struct scatterlist *sg, u32 crci, u32 burst, ++ enum dma_transfer_direction direction) ++{ ++ struct adm_desc_hw_box *box_desc = NULL; ++ struct adm_desc_hw_single *single_desc; ++ u32 remainder = sg_dma_len(sg); ++ u32 rows, row_offset, crci_cmd; ++ u32 mem_addr = sg_dma_address(sg); ++ u32 *incr_addr = &mem_addr; ++ u32 *src, *dst; ++ ++ if (direction == DMA_DEV_TO_MEM) { ++ crci_cmd = ADM_CMD_SRC_CRCI(crci); ++ row_offset = burst; ++ src = &achan->slave.src_addr; ++ dst = &mem_addr; ++ } else { ++ crci_cmd = ADM_CMD_DST_CRCI(crci); ++ row_offset = burst << 16; ++ src = &mem_addr; ++ dst = &achan->slave.dst_addr; ++ } ++ ++ while (remainder >= burst) { ++ box_desc = desc; ++ box_desc->cmd = ADM_CMD_TYPE_BOX | crci_cmd; ++ box_desc->row_offset = row_offset; ++ box_desc->src_addr = *src; ++ box_desc->dst_addr = *dst; ++ ++ rows = remainder / burst; ++ rows = min_t(u32, rows, ADM_MAX_ROWS); ++ box_desc->num_rows = rows << 16 | rows; ++ box_desc->row_len = burst << 16 | burst; ++ ++ *incr_addr += burst * rows; ++ remainder -= burst * rows; ++ desc += sizeof(*box_desc); ++ } ++ ++ /* if leftover bytes, do one single descriptor */ ++ if (remainder) { ++ single_desc = desc; ++ single_desc->cmd = ADM_CMD_TYPE_SINGLE | crci_cmd; ++ single_desc->len = remainder; ++ single_desc->src_addr = *src; ++ single_desc->dst_addr = *dst; ++ desc += sizeof(*single_desc); ++ ++ if (sg_is_last(sg)) ++ single_desc->cmd |= ADM_CMD_LC; ++ } else { ++ if (box_desc && sg_is_last(sg)) ++ box_desc->cmd |= ADM_CMD_LC; ++ } ++ ++ return desc; ++} ++ ++/** ++ * adm_process_non_fc_descriptors - Process descriptors for non-fc xfers ++ * ++ * @achan: ADM channel ++ * @desc: Descriptor memory pointer ++ * @sg: Scatterlist entry ++ * @direction: DMA transfer direction ++ */ ++static void *adm_process_non_fc_descriptors(struct adm_chan *achan, ++ void *desc, struct scatterlist *sg, ++ enum dma_transfer_direction direction) ++{ ++ struct adm_desc_hw_single *single_desc; ++ u32 remainder = sg_dma_len(sg); ++ u32 mem_addr = sg_dma_address(sg); ++ u32 *incr_addr = &mem_addr; ++ u32 *src, *dst; ++ ++ if (direction == DMA_DEV_TO_MEM) { ++ src = &achan->slave.src_addr; ++ dst = &mem_addr; ++ } else { ++ src = &mem_addr; ++ dst = &achan->slave.dst_addr; ++ } ++ ++ do { ++ single_desc = desc; ++ single_desc->cmd = ADM_CMD_TYPE_SINGLE; ++ single_desc->src_addr = *src; ++ single_desc->dst_addr = *dst; ++ single_desc->len = (remainder > ADM_MAX_XFER) ? ++ ADM_MAX_XFER : remainder; ++ ++ remainder -= single_desc->len; ++ *incr_addr += single_desc->len; ++ desc += sizeof(*single_desc); ++ } while (remainder); ++ ++ /* set last command if this is the end of the whole transaction */ ++ if (sg_is_last(sg)) ++ single_desc->cmd |= ADM_CMD_LC; ++ ++ return desc; ++} ++ ++/** ++ * adm_prep_slave_sg - Prep slave sg transaction ++ * ++ * @chan: dma channel ++ * @sgl: scatter gather list ++ * @sg_len: length of sg ++ * @direction: DMA transfer direction ++ * @flags: DMA flags ++ * @context: transfer context (unused) ++ */ ++static struct dma_async_tx_descriptor *adm_prep_slave_sg(struct dma_chan *chan, ++ struct scatterlist *sgl, unsigned int sg_len, ++ enum dma_transfer_direction direction, unsigned long flags, ++ void *context) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct adm_device *adev = achan->adev; ++ struct adm_async_desc *async_desc; ++ struct scatterlist *sg; ++ u32 i, burst; ++ u32 single_count = 0, box_count = 0, crci = 0; ++ void *desc; ++ u32 *cple; ++ int blk_size = 0; ++ ++ if (!is_slave_direction(direction)) { ++ dev_err(adev->dev, "invalid dma direction\n"); ++ return NULL; ++ } ++ ++ /* ++ * get burst value from slave configuration ++ */ ++ burst = (direction == DMA_MEM_TO_DEV) ? ++ achan->slave.dst_maxburst : ++ achan->slave.src_maxburst; ++ ++ /* if using flow control, validate burst and crci values */ ++ if (achan->slave.device_fc) { ++ ++ blk_size = adm_get_blksize(burst); ++ if (blk_size < 0) { ++ dev_err(adev->dev, "invalid burst value: %d\n", ++ burst); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ crci = achan->slave.slave_id & 0xf; ++ if (!crci || achan->slave.slave_id > 0x1f) { ++ dev_err(adev->dev, "invalid crci value\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ } ++ ++ /* iterate through sgs and compute allocation size of structures */ ++ for_each_sg(sgl, sg, sg_len, i) { ++ if (achan->slave.device_fc) { ++ box_count += DIV_ROUND_UP(sg_dma_len(sg) / burst, ++ ADM_MAX_ROWS); ++ if (sg_dma_len(sg) % burst) ++ single_count++; ++ } else { ++ single_count += DIV_ROUND_UP(sg_dma_len(sg), ++ ADM_MAX_XFER); ++ } ++ } ++ ++ async_desc = kzalloc(sizeof(*async_desc), GFP_NOWAIT); ++ if (!async_desc) ++ return ERR_PTR(-ENOMEM); ++ ++ if (crci) ++ async_desc->mux = achan->slave.slave_id & ADM_CRCI_MUX_SEL ? ++ ADM_CRCI_CTL_MUX_SEL : 0; ++ async_desc->crci = crci; ++ async_desc->blk_size = blk_size; ++ async_desc->dma_len = single_count * sizeof(struct adm_desc_hw_single) + ++ box_count * sizeof(struct adm_desc_hw_box) + ++ sizeof(*cple) + 2 * ADM_DESC_ALIGN; ++ ++ async_desc->cpl = dma_alloc_writecombine(adev->dev, async_desc->dma_len, ++ &async_desc->dma_addr, GFP_NOWAIT); ++ ++ if (!async_desc->cpl) { ++ kfree(async_desc); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ async_desc->adev = adev; ++ ++ /* both command list entry and descriptors must be 8 byte aligned */ ++ cple = PTR_ALIGN(async_desc->cpl, ADM_DESC_ALIGN); ++ desc = PTR_ALIGN(cple + 1, ADM_DESC_ALIGN); ++ ++ /* init cmd list */ ++ *cple = ADM_CPLE_LP; ++ *cple |= (desc - async_desc->cpl + async_desc->dma_addr) >> 3; ++ ++ for_each_sg(sgl, sg, sg_len, i) { ++ async_desc->length += sg_dma_len(sg); ++ ++ if (achan->slave.device_fc) ++ desc = adm_process_fc_descriptors(achan, desc, sg, crci, ++ burst, direction); ++ else ++ desc = adm_process_non_fc_descriptors(achan, desc, sg, ++ direction); ++ } ++ ++ return vchan_tx_prep(&achan->vc, &async_desc->vd, flags); ++} ++ ++/** ++ * adm_terminate_all - terminate all transactions on a channel ++ * @achan: adm dma channel ++ * ++ * Dequeues and frees all transactions, aborts current transaction ++ * No callbacks are done ++ * ++ */ ++static int adm_terminate_all(struct dma_chan *chan) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct adm_device *adev = achan->adev; ++ unsigned long flags; ++ LIST_HEAD(head); ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ vchan_get_all_descriptors(&achan->vc, &head); ++ ++ /* send flush command to terminate current transaction */ ++ writel_relaxed(0x0, ++ adev->regs + ADM_CH_FLUSH_STATE0(achan->id, adev->ee)); ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ ++ vchan_dma_desc_free_list(&achan->vc, &head); ++ ++ return 0; ++} ++ ++static int adm_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ unsigned long flag; ++ ++ spin_lock_irqsave(&achan->vc.lock, flag); ++ memcpy(&achan->slave, cfg, sizeof(struct dma_slave_config)); ++ spin_unlock_irqrestore(&achan->vc.lock, flag); ++ ++ return 0; ++} ++ ++/** ++ * adm_start_dma - start next transaction ++ * @achan - ADM dma channel ++ */ ++static void adm_start_dma(struct adm_chan *achan) ++{ ++ struct virt_dma_desc *vd = vchan_next_desc(&achan->vc); ++ struct adm_device *adev = achan->adev; ++ struct adm_async_desc *async_desc; ++ ++ lockdep_assert_held(&achan->vc.lock); ++ ++ if (!vd) ++ return; ++ ++ list_del(&vd->node); ++ ++ /* write next command list out to the CMD FIFO */ ++ async_desc = container_of(vd, struct adm_async_desc, vd); ++ achan->curr_txd = async_desc; ++ ++ /* reset channel error */ ++ achan->error = 0; ++ ++ if (!achan->initialized) { ++ /* enable interrupts */ ++ writel(ADM_CH_CONF_SHADOW_EN | ++ ADM_CH_CONF_PERM_MPU_CONF | ++ ADM_CH_CONF_MPU_DISABLE | ++ ADM_CH_CONF_SEC_DOMAIN(adev->ee), ++ adev->regs + ADM_CH_CONF(achan->id)); ++ ++ writel(ADM_CH_RSLT_CONF_IRQ_EN | ADM_CH_RSLT_CONF_FLUSH_EN, ++ adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee)); ++ ++ achan->initialized = 1; ++ } ++ ++ /* set the crci block size if this transaction requires CRCI */ ++ if (async_desc->crci) { ++ writel(async_desc->mux | async_desc->blk_size, ++ adev->regs + ADM_CRCI_CTL(async_desc->crci, adev->ee)); ++ } ++ ++ /* make sure IRQ enable doesn't get reordered */ ++ wmb(); ++ ++ /* write next command list out to the CMD FIFO */ ++ writel(ALIGN(async_desc->dma_addr, ADM_DESC_ALIGN) >> 3, ++ adev->regs + ADM_CH_CMD_PTR(achan->id, adev->ee)); ++} ++ ++/** ++ * adm_dma_irq - irq handler for ADM controller ++ * @irq: IRQ of interrupt ++ * @data: callback data ++ * ++ * IRQ handler for the bam controller ++ */ ++static irqreturn_t adm_dma_irq(int irq, void *data) ++{ ++ struct adm_device *adev = data; ++ u32 srcs, i; ++ struct adm_async_desc *async_desc; ++ unsigned long flags; ++ ++ srcs = readl_relaxed(adev->regs + ++ ADM_SEC_DOMAIN_IRQ_STATUS(adev->ee)); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) { ++ struct adm_chan *achan = &adev->channels[i]; ++ u32 status, result; ++ ++ if (srcs & BIT(i)) { ++ status = readl_relaxed(adev->regs + ++ ADM_CH_STATUS_SD(i, adev->ee)); ++ ++ /* if no result present, skip */ ++ if (!(status & ADM_CH_STATUS_VALID)) ++ continue; ++ ++ result = readl_relaxed(adev->regs + ++ ADM_CH_RSLT(i, adev->ee)); ++ ++ /* no valid results, skip */ ++ if (!(result & ADM_CH_RSLT_VALID)) ++ continue; ++ ++ /* flag error if transaction was flushed or failed */ ++ if (result & (ADM_CH_RSLT_ERR | ADM_CH_RSLT_FLUSH)) ++ achan->error = 1; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ async_desc = achan->curr_txd; ++ ++ achan->curr_txd = NULL; ++ ++ if (async_desc) { ++ vchan_cookie_complete(&async_desc->vd); ++ ++ /* kick off next DMA */ ++ adm_start_dma(achan); ++ } ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ } ++ } ++ ++ return IRQ_HANDLED; ++} ++ ++/** ++ * adm_tx_status - returns status of transaction ++ * @chan: dma channel ++ * @cookie: transaction cookie ++ * @txstate: DMA transaction state ++ * ++ * Return status of dma transaction ++ */ ++static enum dma_status adm_tx_status(struct dma_chan *chan, dma_cookie_t cookie, ++ struct dma_tx_state *txstate) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ struct virt_dma_desc *vd; ++ enum dma_status ret; ++ unsigned long flags; ++ size_t residue = 0; ++ ++ ret = dma_cookie_status(chan, cookie, txstate); ++ if (ret == DMA_COMPLETE || !txstate) ++ return ret; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ ++ vd = vchan_find_desc(&achan->vc, cookie); ++ if (vd) ++ residue = container_of(vd, struct adm_async_desc, vd)->length; ++ ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++ ++ /* ++ * residue is either the full length if it is in the issued list, or 0 ++ * if it is in progress. We have no reliable way of determining ++ * anything inbetween ++ */ ++ dma_set_residue(txstate, residue); ++ ++ if (achan->error) ++ return DMA_ERROR; ++ ++ return ret; ++} ++ ++/** ++ * adm_issue_pending - starts pending transactions ++ * @chan: dma channel ++ * ++ * Issues all pending transactions and starts DMA ++ */ ++static void adm_issue_pending(struct dma_chan *chan) ++{ ++ struct adm_chan *achan = to_adm_chan(chan); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&achan->vc.lock, flags); ++ ++ if (vchan_issue_pending(&achan->vc) && !achan->curr_txd) ++ adm_start_dma(achan); ++ spin_unlock_irqrestore(&achan->vc.lock, flags); ++} ++ ++/** ++ * adm_dma_free_desc - free descriptor memory ++ * @vd: virtual descriptor ++ * ++ */ ++static void adm_dma_free_desc(struct virt_dma_desc *vd) ++{ ++ struct adm_async_desc *async_desc = container_of(vd, ++ struct adm_async_desc, vd); ++ ++ dma_free_writecombine(async_desc->adev->dev, async_desc->dma_len, ++ async_desc->cpl, async_desc->dma_addr); ++ kfree(async_desc); ++} ++ ++static void adm_channel_init(struct adm_device *adev, struct adm_chan *achan, ++ u32 index) ++{ ++ achan->id = index; ++ achan->adev = adev; ++ ++ vchan_init(&achan->vc, &adev->common); ++ achan->vc.desc_free = adm_dma_free_desc; ++} ++ ++static int adm_dma_probe(struct platform_device *pdev) ++{ ++ struct adm_device *adev; ++ struct resource *iores; ++ int ret; ++ u32 i; ++ ++ adev = devm_kzalloc(&pdev->dev, sizeof(*adev), GFP_KERNEL); ++ if (!adev) ++ return -ENOMEM; ++ ++ adev->dev = &pdev->dev; ++ ++ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ adev->regs = devm_ioremap_resource(&pdev->dev, iores); ++ if (IS_ERR(adev->regs)) ++ return PTR_ERR(adev->regs); ++ ++ adev->irq = platform_get_irq(pdev, 0); ++ if (adev->irq < 0) ++ return adev->irq; ++ ++ ret = of_property_read_u32(pdev->dev.of_node, "qcom,ee", &adev->ee); ++ if (ret) { ++ dev_err(adev->dev, "Execution environment unspecified\n"); ++ return ret; ++ } ++ ++ adev->core_clk = devm_clk_get(adev->dev, "core"); ++ if (IS_ERR(adev->core_clk)) ++ return PTR_ERR(adev->core_clk); ++ ++ ret = clk_prepare_enable(adev->core_clk); ++ if (ret) { ++ dev_err(adev->dev, "failed to prepare/enable core clock\n"); ++ return ret; ++ } ++ ++ adev->iface_clk = devm_clk_get(adev->dev, "iface"); ++ if (IS_ERR(adev->iface_clk)) { ++ ret = PTR_ERR(adev->iface_clk); ++ goto err_disable_core_clk; ++ } ++ ++ ret = clk_prepare_enable(adev->iface_clk); ++ if (ret) { ++ dev_err(adev->dev, "failed to prepare/enable iface clock\n"); ++ goto err_disable_core_clk; ++ } ++ ++ adev->clk_reset = devm_reset_control_get(&pdev->dev, "clk"); ++ if (IS_ERR(adev->clk_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 reset\n"); ++ ret = PTR_ERR(adev->clk_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c0_reset = devm_reset_control_get(&pdev->dev, "c0"); ++ if (IS_ERR(adev->c0_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C0 reset\n"); ++ ret = PTR_ERR(adev->c0_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c1_reset = devm_reset_control_get(&pdev->dev, "c1"); ++ if (IS_ERR(adev->c1_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C1 reset\n"); ++ ret = PTR_ERR(adev->c1_reset); ++ goto err_disable_clks; ++ } ++ ++ adev->c2_reset = devm_reset_control_get(&pdev->dev, "c2"); ++ if (IS_ERR(adev->c2_reset)) { ++ dev_err(adev->dev, "failed to get ADM0 C2 reset\n"); ++ ret = PTR_ERR(adev->c2_reset); ++ goto err_disable_clks; ++ } ++ ++ reset_control_assert(adev->clk_reset); ++ reset_control_assert(adev->c0_reset); ++ reset_control_assert(adev->c1_reset); ++ reset_control_assert(adev->c2_reset); ++ ++ reset_control_deassert(adev->clk_reset); ++ reset_control_deassert(adev->c0_reset); ++ reset_control_deassert(adev->c1_reset); ++ reset_control_deassert(adev->c2_reset); ++ ++ adev->channels = devm_kcalloc(adev->dev, ADM_MAX_CHANNELS, ++ sizeof(*adev->channels), GFP_KERNEL); ++ ++ if (!adev->channels) { ++ ret = -ENOMEM; ++ goto err_disable_clks; ++ } ++ ++ /* allocate and initialize channels */ ++ INIT_LIST_HEAD(&adev->common.channels); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) ++ adm_channel_init(adev, &adev->channels[i], i); ++ ++ /* reset CRCIs */ ++ for (i = 0; i < 16; i++) ++ writel(ADM_CRCI_CTL_RST, adev->regs + ++ ADM_CRCI_CTL(i, adev->ee)); ++ ++ /* configure client interfaces */ ++ writel(ADM_CI_RANGE_START(0x40) | ADM_CI_RANGE_END(0xb0) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(0)); ++ writel(ADM_CI_RANGE_START(0x2a) | ADM_CI_RANGE_END(0x2c) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(1)); ++ writel(ADM_CI_RANGE_START(0x12) | ADM_CI_RANGE_END(0x28) | ++ ADM_CI_BURST_8_WORDS, adev->regs + ADM_CI_CONF(2)); ++ writel(ADM_GP_CTL_LP_EN | ADM_GP_CTL_LP_CNT(0xf), ++ adev->regs + ADM_GP_CTL); ++ ++ ret = devm_request_irq(adev->dev, adev->irq, adm_dma_irq, ++ 0, "adm_dma", adev); ++ if (ret) ++ goto err_disable_clks; ++ ++ platform_set_drvdata(pdev, adev); ++ ++ adev->common.dev = adev->dev; ++ adev->common.dev->dma_parms = &adev->dma_parms; ++ ++ /* set capabilities */ ++ dma_cap_zero(adev->common.cap_mask); ++ dma_cap_set(DMA_SLAVE, adev->common.cap_mask); ++ dma_cap_set(DMA_PRIVATE, adev->common.cap_mask); ++ ++ /* initialize dmaengine apis */ ++ adev->common.directions = BIT(DMA_DEV_TO_MEM | DMA_MEM_TO_DEV); ++ adev->common.residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; ++ adev->common.src_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES; ++ adev->common.dst_addr_widths = DMA_SLAVE_BUSWIDTH_4_BYTES; ++ adev->common.device_free_chan_resources = adm_free_chan; ++ adev->common.device_prep_slave_sg = adm_prep_slave_sg; ++ adev->common.device_issue_pending = adm_issue_pending; ++ adev->common.device_tx_status = adm_tx_status; ++ adev->common.device_terminate_all = adm_terminate_all; ++ adev->common.device_config = adm_slave_config; ++ ++ ret = dma_async_device_register(&adev->common); ++ if (ret) { ++ dev_err(adev->dev, "failed to register dma async device\n"); ++ goto err_disable_clks; ++ } ++ ++ ret = of_dma_controller_register(pdev->dev.of_node, ++ of_dma_xlate_by_chan_id, ++ &adev->common); ++ if (ret) ++ goto err_unregister_dma; ++ ++ return 0; ++ ++err_unregister_dma: ++ dma_async_device_unregister(&adev->common); ++err_disable_clks: ++ clk_disable_unprepare(adev->iface_clk); ++err_disable_core_clk: ++ clk_disable_unprepare(adev->core_clk); ++ ++ return ret; ++} ++ ++static int adm_dma_remove(struct platform_device *pdev) ++{ ++ struct adm_device *adev = platform_get_drvdata(pdev); ++ struct adm_chan *achan; ++ u32 i; ++ ++ of_dma_controller_free(pdev->dev.of_node); ++ dma_async_device_unregister(&adev->common); ++ ++ for (i = 0; i < ADM_MAX_CHANNELS; i++) { ++ achan = &adev->channels[i]; ++ ++ /* mask IRQs for this channel/EE pair */ ++ writel(0, adev->regs + ADM_CH_RSLT_CONF(achan->id, adev->ee)); ++ ++ adm_terminate_all(&adev->channels[i].vc.chan); ++ } ++ ++ devm_free_irq(adev->dev, adev->irq, adev); ++ ++ clk_disable_unprepare(adev->core_clk); ++ clk_disable_unprepare(adev->iface_clk); ++ ++ return 0; ++} ++ ++static const struct of_device_id adm_of_match[] = { ++ { .compatible = "qcom,adm", }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, adm_of_match); ++ ++static struct platform_driver adm_dma_driver = { ++ .probe = adm_dma_probe, ++ .remove = adm_dma_remove, ++ .driver = { ++ .name = "adm-dma-engine", ++ .of_match_table = adm_of_match, ++ }, ++}; ++ ++module_platform_driver(adm_dma_driver); ++ ++MODULE_AUTHOR("Andy Gross "); ++MODULE_DESCRIPTION("QCOM ADM DMA engine driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/dma/Makefile ++++ b/drivers/dma/Makefile +@@ -54,3 +54,4 @@ obj-$(CONFIG_NBPFAXI_DMA) += nbpfaxi.o + obj-$(CONFIG_DMA_SUN6I) += sun6i-dma.o + obj-$(CONFIG_IMG_MDC_DMA) += img-mdc-dma.o + obj-$(CONFIG_XGENE_DMA) += xgene-dma.o ++obj-$(CONFIG_QCOM_ADM) += qcom_adm.o diff --git a/target/linux/ipq806x/patches-4.1/157-ARM-DT-ipq8064-Add-ADM-device-node.patch b/target/linux/ipq806x/patches-4.1/157-ARM-DT-ipq8064-Add-ADM-device-node.patch new file mode 100644 index 0000000..159d912 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/157-ARM-DT-ipq8064-Add-ADM-device-node.patch @@ -0,0 +1,42 @@ +From 1fb18acab2d71e7e4efd9c10492edb1baf84dcc0 Mon Sep 17 00:00:00 2001 +From: Andy Gross +Date: Wed, 20 May 2015 15:41:07 +0530 +Subject: [PATCH] ARM: DT: ipq8064: Add ADM device node + +This patch adds support for the ADM DMA on the IPQ8064 SOC + +Signed-off-by: Andy Gross +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 4 ++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 21 +++++++++++++++++++++ + 2 files changed, 25 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -600,6 +600,26 @@ + + status = "disabled"; + }; ++ ++ adm_dma: dma@18300000 { ++ compatible = "qcom,adm"; ++ reg = <0x18300000 0x100000>; ++ interrupts = <0 170 0>; ++ #dma-cells = <1>; ++ ++ clocks = <&gcc ADM0_CLK>, <&gcc ADM0_PBUS_CLK>; ++ clock-names = "core", "iface"; ++ ++ resets = <&gcc ADM0_RESET>, ++ <&gcc ADM0_PBUS_RESET>, ++ <&gcc ADM0_C0_RESET>, ++ <&gcc ADM0_C1_RESET>, ++ <&gcc ADM0_C2_RESET>; ++ reset-names = "clk", "pbus", "c0", "c1", "c2"; ++ qcom,ee = <0>; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.1/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch b/target/linux/ipq806x/patches-4.1/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch new file mode 100644 index 0000000..088b8cb --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/161-mtd-nand-Create-a-BBT-flag-to-access-bad-block-markers-in-raw-mode.patch @@ -0,0 +1,84 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3, + 1/5] mtd: nand: Create a BBT flag to access bad block markers in raw + mode +From: Archit Taneja +X-Patchwork-Id: 6927081 +Message-Id: <1438578498-32254-2-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja +Date: Mon, 3 Aug 2015 10:38:14 +0530 + +Some controllers can access the factory bad block marker from OOB only +when they read it in raw mode. When ECC is enabled, these controllers +discard reading/writing bad block markers, preventing access to them +altogether. + +The bbt driver assumes MTD_OPS_PLACE_OOB when scanning for bad blocks. +This results in the nand driver's ecc->read_oob() op to be called, which +works with ECC enabled. + +Create a new BBT option flag that tells nand_bbt to force the mode to +MTD_OPS_RAW. This would result in the correct op being called for the +underlying nand controller driver. + +Reviewed-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +drivers/mtd/nand/nand_base.c | 6 +++++- + drivers/mtd/nand/nand_bbt.c | 6 +++++- + include/linux/mtd/bbm.h | 7 +++++++ + 3 files changed, 17 insertions(+), 2 deletions(-) + +--- a/drivers/mtd/nand/nand_base.c ++++ b/drivers/mtd/nand/nand_base.c +@@ -395,7 +395,11 @@ static int nand_default_block_markbad(st + } else { + ops.len = ops.ooblen = 1; + } +- ops.mode = MTD_OPS_PLACE_OOB; ++ ++ if (unlikely(chip->bbt_options & NAND_BBT_ACCESS_BBM_RAW)) ++ ops.mode = MTD_OPS_RAW; ++ else ++ ops.mode = MTD_OPS_PLACE_OOB; + + /* Write to first/last page(s) if necessary */ + if (chip->bbt_options & NAND_BBT_SCANLASTPAGE) +--- a/drivers/mtd/nand/nand_bbt.c ++++ b/drivers/mtd/nand/nand_bbt.c +@@ -423,7 +423,11 @@ static int scan_block_fast(struct mtd_in + ops.oobbuf = buf; + ops.ooboffs = 0; + ops.datbuf = NULL; +- ops.mode = MTD_OPS_PLACE_OOB; ++ ++ if (unlikely(bd->options & NAND_BBT_ACCESS_BBM_RAW)) ++ ops.mode = MTD_OPS_RAW; ++ else ++ ops.mode = MTD_OPS_PLACE_OOB; + + for (j = 0; j < numpages; j++) { + /* +--- a/include/linux/mtd/bbm.h ++++ b/include/linux/mtd/bbm.h +@@ -116,6 +116,13 @@ struct nand_bbt_descr { + #define NAND_BBT_NO_OOB_BBM 0x00080000 + + /* ++ * Force MTD_OPS_RAW mode when trying to access bad block markes from OOB. To ++ * be used by controllers which can access BBM only when ECC is disabled, i.e, ++ * when in RAW access mode ++ */ ++#define NAND_BBT_ACCESS_BBM_RAW 0x00100000 ++ ++/* + * Flag set by nand_create_default_bbt_descr(), marking that the nand_bbt_descr + * was allocated dynamicaly and must be freed in nand_release(). Has no meaning + * in nand_chip.bbt_options. diff --git a/target/linux/ipq806x/patches-4.1/162-mtd-nand-Qualcomm-NAND-controller-driver.patch b/target/linux/ipq806x/patches-4.1/162-mtd-nand-Qualcomm-NAND-controller-driver.patch new file mode 100644 index 0000000..4be7ecb --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/162-mtd-nand-Qualcomm-NAND-controller-driver.patch @@ -0,0 +1,2024 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,2/5] mtd: nand: Qualcomm NAND controller driver +From: Archit Taneja +X-Patchwork-Id: 6927101 +Message-Id: <1438578498-32254-3-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja +Date: Mon, 3 Aug 2015 10:38:15 +0530 + +The Qualcomm NAND controller is found in SoCs like IPQ806x, MSM7xx, +MDM9x15 series. + +It exists as a sub block inside the IPs EBI2 (External Bus Interface 2) +and QPIC (Qualcomm Parallel Interface Controller). These IPs provide a +broader interface for external slow peripheral devices such as LCD and +NAND/NOR flash memory or SRAM like interfaces. + +We add support for the NAND controller found within EBI2. For the SoCs +of our interest, we only use the NAND controller within EBI2. Therefore, +it's safe for us to assume that the NAND controller is a standalone block +within the SoC. + +The controller supports 512B, 2kB, 4kB and 8kB page 8-bit and 16-bit NAND +flash devices. It contains a HW ECC block that supports BCH ECC (4, 8 and +16 bit correction/step) and RS ECC(4 bit correction/step) that covers main +and spare data. The controller contains an internal 512 byte page buffer +to which we read/write via DMA. The EBI2 type NAND controller uses ADM DMA +for register read/write and data transfers. The controller performs page +reads and writes at a codeword/step level of 512 bytes. It can support up +to 2 external chips of different configurations. + +The driver prepares register read and write configuration descriptors for +each codeword, followed by data descriptors to read or write data from the +controller's internal buffer. It uses a single ADM DMA channel that we get +via dmaengine API. The controller requires 2 ADM CRCIs for command and +data flow control. These are passed via DT. + +The ecc layout used by the controller is syndrome like, but we can't use +the standard syndrome ecc ops because of several reasons. First, the amount +of data bytes covered by ecc isn't same in each step. Second, writing to +free oob space requires us writing to the entire step in which the oob +lies. This forces us to create our own ecc ops. + +One more difference is how the controller accesses the bad block marker. +The controller ignores reading the marker when ECC is enabled. ECC needs +to be explicity disabled to read or write to the bad block marker. For +this reason, we use the newly created flag NAND_BBT_ACCESS_BBM_RAW to +read the factory provided bad block markers. + +v3: +- Refactor dma functions for maximum reuse +- Use dma_slave_confing on stack +- optimize and clean upempty_page_fixup using memchr_inv +- ensure portability with dma register reads using le32_* funcs +- use NAND_USE_BOUNCE_BUFFER instead of doing it ourselves +- fix handling of return values of dmaengine funcs +- constify wherever possible +- Remove dependency on ADM DMA in Kconfig +- Misc fixes and clean ups + +v2: +- Use new BBT flag that allows us to read BBM in raw mode +- reduce memcpy-s in the driver +- some refactor and clean ups because of above changes + +Reviewed-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +drivers/mtd/nand/Kconfig | 7 + + drivers/mtd/nand/Makefile | 1 + + drivers/mtd/nand/qcom_nandc.c | 1913 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 1921 insertions(+) + create mode 100644 drivers/mtd/nand/qcom_nandc.c + +--- a/drivers/mtd/nand/Kconfig ++++ b/drivers/mtd/nand/Kconfig +@@ -530,4 +530,11 @@ config MTD_NAND_HISI504 + help + Enables support for NAND controller on Hisilicon SoC Hip04. + ++config MTD_NAND_QCOM ++ tristate "Support for NAND on QCOM SoCs" ++ depends on ARCH_QCOM ++ help ++ Enables support for NAND flash chips on SoCs containing the EBI2 NAND ++ controller. This controller is found on IPQ806x SoC. ++ + endif # MTD_NAND +--- /dev/null ++++ b/drivers/mtd/nand/qcom_nandc.c +@@ -0,0 +1,1918 @@ ++/* ++ * Copyright (c) 2015, The Linux Foundation. All rights reserved. ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* NANDc reg offsets */ ++#define NAND_FLASH_CMD 0x00 ++#define NAND_ADDR0 0x04 ++#define NAND_ADDR1 0x08 ++#define NAND_FLASH_CHIP_SELECT 0x0c ++#define NAND_EXEC_CMD 0x10 ++#define NAND_FLASH_STATUS 0x14 ++#define NAND_BUFFER_STATUS 0x18 ++#define NAND_DEV0_CFG0 0x20 ++#define NAND_DEV0_CFG1 0x24 ++#define NAND_DEV0_ECC_CFG 0x28 ++#define NAND_DEV1_ECC_CFG 0x2c ++#define NAND_DEV1_CFG0 0x30 ++#define NAND_DEV1_CFG1 0x34 ++#define NAND_READ_ID 0x40 ++#define NAND_READ_STATUS 0x44 ++#define NAND_DEV_CMD0 0xa0 ++#define NAND_DEV_CMD1 0xa4 ++#define NAND_DEV_CMD2 0xa8 ++#define NAND_DEV_CMD_VLD 0xac ++#define SFLASHC_BURST_CFG 0xe0 ++#define NAND_ERASED_CW_DETECT_CFG 0xe8 ++#define NAND_ERASED_CW_DETECT_STATUS 0xec ++#define NAND_EBI2_ECC_BUF_CFG 0xf0 ++#define FLASH_BUF_ACC 0x100 ++ ++#define NAND_CTRL 0xf00 ++#define NAND_VERSION 0xf08 ++#define NAND_READ_LOCATION_0 0xf20 ++#define NAND_READ_LOCATION_1 0xf24 ++ ++/* dummy register offsets, used by write_reg_dma */ ++#define NAND_DEV_CMD1_RESTORE 0xdead ++#define NAND_DEV_CMD_VLD_RESTORE 0xbeef ++ ++/* NAND_FLASH_CMD bits */ ++#define PAGE_ACC BIT(4) ++#define LAST_PAGE BIT(5) ++ ++/* NAND_FLASH_CHIP_SELECT bits */ ++#define NAND_DEV_SEL 0 ++#define DM_EN BIT(2) ++ ++/* NAND_FLASH_STATUS bits */ ++#define FS_OP_ERR BIT(4) ++#define FS_READY_BSY_N BIT(5) ++#define FS_MPU_ERR BIT(8) ++#define FS_DEVICE_STS_ERR BIT(16) ++#define FS_DEVICE_WP BIT(23) ++ ++/* NAND_BUFFER_STATUS bits */ ++#define BS_UNCORRECTABLE_BIT BIT(8) ++#define BS_CORRECTABLE_ERR_MSK 0x1f ++ ++/* NAND_DEVn_CFG0 bits */ ++#define DISABLE_STATUS_AFTER_WRITE 4 ++#define CW_PER_PAGE 6 ++#define UD_SIZE_BYTES 9 ++#define ECC_PARITY_SIZE_BYTES_RS 19 ++#define SPARE_SIZE_BYTES 23 ++#define NUM_ADDR_CYCLES 27 ++#define STATUS_BFR_READ 30 ++#define SET_RD_MODE_AFTER_STATUS 31 ++ ++/* NAND_DEVn_CFG0 bits */ ++#define DEV0_CFG1_ECC_DISABLE 0 ++#define WIDE_FLASH 1 ++#define NAND_RECOVERY_CYCLES 2 ++#define CS_ACTIVE_BSY 5 ++#define BAD_BLOCK_BYTE_NUM 6 ++#define BAD_BLOCK_IN_SPARE_AREA 16 ++#define WR_RD_BSY_GAP 17 ++#define ENABLE_BCH_ECC 27 ++ ++/* NAND_DEV0_ECC_CFG bits */ ++#define ECC_CFG_ECC_DISABLE 0 ++#define ECC_SW_RESET 1 ++#define ECC_MODE 4 ++#define ECC_PARITY_SIZE_BYTES_BCH 8 ++#define ECC_NUM_DATA_BYTES 16 ++#define ECC_FORCE_CLK_OPEN 30 ++ ++/* NAND_DEV_CMD1 bits */ ++#define READ_ADDR 0 ++ ++/* NAND_DEV_CMD_VLD bits */ ++#define READ_START_VLD 0 ++ ++/* NAND_EBI2_ECC_BUF_CFG bits */ ++#define NUM_STEPS 0 ++ ++/* NAND_ERASED_CW_DETECT_CFG bits */ ++#define ERASED_CW_ECC_MASK 1 ++#define AUTO_DETECT_RES 0 ++#define MASK_ECC (1 << ERASED_CW_ECC_MASK) ++#define RESET_ERASED_DET (1 << AUTO_DETECT_RES) ++#define ACTIVE_ERASED_DET (0 << AUTO_DETECT_RES) ++#define CLR_ERASED_PAGE_DET (RESET_ERASED_DET | MASK_ECC) ++#define SET_ERASED_PAGE_DET (ACTIVE_ERASED_DET | MASK_ECC) ++ ++/* NAND_ERASED_CW_DETECT_STATUS bits */ ++#define PAGE_ALL_ERASED BIT(7) ++#define CODEWORD_ALL_ERASED BIT(6) ++#define PAGE_ERASED BIT(5) ++#define CODEWORD_ERASED BIT(4) ++#define ERASED_PAGE (PAGE_ALL_ERASED | PAGE_ERASED) ++#define ERASED_CW (CODEWORD_ALL_ERASED | CODEWORD_ERASED) ++ ++/* Version Mask */ ++#define NAND_VERSION_MAJOR_MASK 0xf0000000 ++#define NAND_VERSION_MAJOR_SHIFT 28 ++#define NAND_VERSION_MINOR_MASK 0x0fff0000 ++#define NAND_VERSION_MINOR_SHIFT 16 ++ ++/* NAND OP_CMDs */ ++#define PAGE_READ 0x2 ++#define PAGE_READ_WITH_ECC 0x3 ++#define PAGE_READ_WITH_ECC_SPARE 0x4 ++#define PROGRAM_PAGE 0x6 ++#define PAGE_PROGRAM_WITH_ECC 0x7 ++#define PROGRAM_PAGE_SPARE 0x9 ++#define BLOCK_ERASE 0xa ++#define FETCH_ID 0xb ++#define RESET_DEVICE 0xd ++ ++/* ++ * the NAND controller performs reads/writes with ECC in 516 byte chunks. ++ * the driver calls the chunks 'step' or 'codeword' interchangeably ++ */ ++#define NANDC_STEP_SIZE 512 ++ ++/* ++ * the largest page size we support is 8K, this will have 16 steps/codewords ++ * of 512 bytes each ++ */ ++#define MAX_NUM_STEPS (SZ_8K / NANDC_STEP_SIZE) ++ ++/* we read at most 3 registers per codeword scan */ ++#define MAX_REG_RD (3 * MAX_NUM_STEPS) ++ ++/* ECC modes */ ++#define ECC_NONE BIT(0) ++#define ECC_RS_4BIT BIT(1) ++#define ECC_BCH_4BIT BIT(2) ++#define ECC_BCH_8BIT BIT(3) ++ ++struct desc_info { ++ struct list_head list; ++ ++ enum dma_transfer_direction dir; ++ struct scatterlist sgl; ++ struct dma_async_tx_descriptor *dma_desc; ++}; ++ ++/* ++ * holds the current register values that we want to write. acts as a contiguous ++ * chunk of memory which we use to write the controller registers through DMA. ++ */ ++struct nandc_regs { ++ u32 cmd; ++ u32 addr0; ++ u32 addr1; ++ u32 chip_sel; ++ u32 exec; ++ ++ u32 cfg0; ++ u32 cfg1; ++ u32 ecc_bch_cfg; ++ ++ u32 clrflashstatus; ++ u32 clrreadstatus; ++ ++ u32 cmd1; ++ u32 vld; ++ ++ u32 orig_cmd1; ++ u32 orig_vld; ++ ++ u32 ecc_buf_cfg; ++}; ++ ++/* ++ * @cmd_crci: ADM DMA CRCI for command flow control ++ * @data_crci: ADM DMA CRCI for data flow control ++ * @list: DMA descriptor list (list of desc_infos) ++ * @dma_done: completion param to denote end of last ++ * descriptor in the list ++ * @data_buffer: our local DMA buffer for page read/writes, ++ * used when we can't use the buffer provided ++ * by upper layers directly ++ * @buf_size/count/start: markers for chip->read_buf/write_buf functions ++ * @reg_read_buf: buffer for reading register data via DMA ++ * @reg_read_pos: marker for data read in reg_read_buf ++ * @cfg0, cfg1, cfg0_raw..: NANDc register configurations needed for ++ * ecc/non-ecc mode for the current nand flash ++ * device ++ * @regs: a contiguous chunk of memory for DMA register ++ * writes ++ * @ecc_strength: 4 bit or 8 bit ecc, received via DT ++ * @bus_width: 8 bit or 16 bit NAND bus width, received via DT ++ * @ecc_modes: supported ECC modes by the current controller, ++ * initialized via DT match data ++ * @cw_size: the number of bytes in a single step/codeword ++ * of a page, consisting of all data, ecc, spare ++ * and reserved bytes ++ * @cw_data: the number of bytes within a codeword protected ++ * by ECC ++ * @bch_enabled: flag to tell whether BCH or RS ECC mode is used ++ * @status: value to be returned if NAND_CMD_STATUS command ++ * is executed ++ */ ++struct qcom_nandc_data { ++ struct platform_device *pdev; ++ struct device *dev; ++ ++ void __iomem *base; ++ struct resource *res; ++ ++ struct clk *core_clk; ++ struct clk *aon_clk; ++ ++ /* DMA stuff */ ++ struct dma_chan *chan; ++ struct dma_slave_config slave_conf; ++ unsigned int cmd_crci; ++ unsigned int data_crci; ++ struct list_head list; ++ struct completion dma_done; ++ ++ /* MTD stuff */ ++ struct nand_chip chip; ++ struct mtd_info mtd; ++ ++ /* local data buffer and markers */ ++ u8 *data_buffer; ++ int buf_size; ++ int buf_count; ++ int buf_start; ++ ++ /* local buffer to read back registers */ ++ u32 *reg_read_buf; ++ int reg_read_pos; ++ ++ /* required configs */ ++ u32 cfg0, cfg1; ++ u32 cfg0_raw, cfg1_raw; ++ u32 ecc_buf_cfg; ++ u32 ecc_bch_cfg; ++ u32 clrflashstatus; ++ u32 clrreadstatus; ++ u32 sflashc_burst_cfg; ++ u32 cmd1, vld; ++ ++ /* register state */ ++ struct nandc_regs *regs; ++ ++ /* things we get from DT */ ++ int ecc_strength; ++ int bus_width; ++ ++ u32 ecc_modes; ++ ++ /* misc params */ ++ int cw_size; ++ int cw_data; ++ bool use_ecc; ++ bool bch_enabled; ++ u8 status; ++ int last_command; ++}; ++ ++static inline u32 nandc_read(struct qcom_nandc_data *this, int offset) ++{ ++ return ioread32(this->base + offset); ++} ++ ++static inline void nandc_write(struct qcom_nandc_data *this, int offset, ++ u32 val) ++{ ++ iowrite32(val, this->base + offset); ++} ++ ++/* helper to configure address register values */ ++static void set_address(struct qcom_nandc_data *this, u16 column, int page) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nandc_regs *regs = this->regs; ++ ++ if (chip->options & NAND_BUSWIDTH_16) ++ column >>= 1; ++ ++ regs->addr0 = page << 16 | column; ++ regs->addr1 = page >> 16 & 0xff; ++} ++ ++/* ++ * update_rw_regs: set up read/write register values, these will be ++ * written to the NAND controller registers via DMA ++ * ++ * @num_cw: number of steps for the read/write operation ++ * @read: read or write operation ++ */ ++static void update_rw_regs(struct qcom_nandc_data *this, int num_cw, bool read) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ if (read) { ++ if (this->use_ecc) ++ regs->cmd = PAGE_READ_WITH_ECC | PAGE_ACC | LAST_PAGE; ++ else ++ regs->cmd = PAGE_READ | PAGE_ACC | LAST_PAGE; ++ } else { ++ regs->cmd = PROGRAM_PAGE | PAGE_ACC | LAST_PAGE; ++ } ++ ++ if (this->use_ecc) { ++ regs->cfg0 = (this->cfg0 & ~(7U << CW_PER_PAGE)) | ++ (num_cw - 1) << CW_PER_PAGE; ++ ++ regs->cfg1 = this->cfg1; ++ regs->ecc_bch_cfg = this->ecc_bch_cfg; ++ } else { ++ regs->cfg0 = (this->cfg0_raw & ~(7U << CW_PER_PAGE)) | ++ (num_cw - 1) << CW_PER_PAGE; ++ ++ regs->cfg1 = this->cfg1_raw; ++ regs->ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE; ++ } ++ ++ regs->ecc_buf_cfg = this->ecc_buf_cfg; ++ regs->clrflashstatus = this->clrflashstatus; ++ regs->clrreadstatus = this->clrreadstatus; ++ regs->exec = 1; ++} ++ ++static int prep_dma_desc(struct qcom_nandc_data *this, bool read, int reg_off, ++ const void *vaddr, int size, bool flow_control) ++{ ++ struct desc_info *desc; ++ struct dma_async_tx_descriptor *dma_desc; ++ struct scatterlist *sgl; ++ struct dma_slave_config slave_conf; ++ int r; ++ ++ desc = kzalloc(sizeof(*desc), GFP_KERNEL); ++ if (!desc) ++ return -ENOMEM; ++ ++ list_add_tail(&desc->list, &this->list); ++ ++ sgl = &desc->sgl; ++ ++ sg_init_one(sgl, vaddr, size); ++ ++ desc->dir = read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; ++ ++ r = dma_map_sg(this->dev, sgl, 1, desc->dir); ++ if (r == 0) { ++ r = -ENOMEM; ++ goto err; ++ } ++ ++ memset(&slave_conf, 0x00, sizeof(slave_conf)); ++ ++ slave_conf.device_fc = flow_control; ++ if (read) { ++ slave_conf.src_maxburst = 16; ++ slave_conf.src_addr = this->res->start + reg_off; ++ slave_conf.slave_id = this->data_crci; ++ } else { ++ slave_conf.dst_maxburst = 16; ++ slave_conf.dst_addr = this->res->start + reg_off; ++ slave_conf.slave_id = this->cmd_crci; ++ } ++ ++ r = dmaengine_slave_config(this->chan, &slave_conf); ++ if (r) { ++ dev_err(this->dev, "failed to configure dma channel\n"); ++ goto err; ++ } ++ ++ dma_desc = dmaengine_prep_slave_sg(this->chan, sgl, 1, desc->dir, 0); ++ if (!dma_desc) { ++ dev_err(this->dev, "failed to prepare desc\n"); ++ r = -EINVAL; ++ goto err; ++ } ++ ++ desc->dma_desc = dma_desc; ++ ++ return 0; ++err: ++ kfree(desc); ++ ++ return r; ++} ++ ++/* ++ * read_reg_dma: prepares a descriptor to read a given number of ++ * contiguous registers to the reg_read_buf pointer ++ * ++ * @first: offset of the first register in the contiguous block ++ * @num_regs: number of registers to read ++ */ ++static int read_reg_dma(struct qcom_nandc_data *this, int first, int num_regs) ++{ ++ bool flow_control = false; ++ void *vaddr; ++ int size; ++ ++ if (first == NAND_READ_ID || first == NAND_FLASH_STATUS) ++ flow_control = true; ++ ++ size = num_regs * sizeof(u32); ++ vaddr = this->reg_read_buf + this->reg_read_pos; ++ this->reg_read_pos += num_regs; ++ ++ return prep_dma_desc(this, true, first, vaddr, size, flow_control); ++} ++ ++/* ++ * write_reg_dma: prepares a descriptor to write a given number of ++ * contiguous registers ++ * ++ * @first: offset of the first register in the contiguous block ++ * @num_regs: number of registers to write ++ */ ++static int write_reg_dma(struct qcom_nandc_data *this, int first, int num_regs) ++{ ++ bool flow_control = false; ++ struct nandc_regs *regs = this->regs; ++ void *vaddr; ++ int size; ++ ++ switch (first) { ++ case NAND_FLASH_CMD: ++ vaddr = ®s->cmd; ++ flow_control = true; ++ break; ++ case NAND_EXEC_CMD: ++ vaddr = ®s->exec; ++ break; ++ case NAND_FLASH_STATUS: ++ vaddr = ®s->clrflashstatus; ++ break; ++ case NAND_DEV0_CFG0: ++ vaddr = ®s->cfg0; ++ break; ++ case NAND_READ_STATUS: ++ vaddr = ®s->clrreadstatus; ++ break; ++ case NAND_DEV_CMD1: ++ vaddr = ®s->cmd1; ++ break; ++ case NAND_DEV_CMD1_RESTORE: ++ first = NAND_DEV_CMD1; ++ vaddr = ®s->orig_cmd1; ++ break; ++ case NAND_DEV_CMD_VLD: ++ vaddr = ®s->vld; ++ break; ++ case NAND_DEV_CMD_VLD_RESTORE: ++ first = NAND_DEV_CMD_VLD; ++ vaddr = ®s->orig_vld; ++ break; ++ case NAND_EBI2_ECC_BUF_CFG: ++ vaddr = ®s->ecc_buf_cfg; ++ break; ++ default: ++ dev_err(this->dev, "invalid starting register\n"); ++ return -EINVAL; ++ } ++ ++ size = num_regs * sizeof(u32); ++ ++ return prep_dma_desc(this, false, first, vaddr, size, flow_control); ++} ++ ++/* ++ * read_data_dma: prepares a DMA descriptor to transfer data from the ++ * controller's internal buffer to the buffer 'vaddr' ++ * ++ * @reg_off: offset within the controller's data buffer ++ * @vaddr: virtual address of the buffer we want to write to ++ * @size: DMA transaction size in bytes ++ */ ++static int read_data_dma(struct qcom_nandc_data *this, int reg_off, ++ const u8 *vaddr, int size) ++{ ++ return prep_dma_desc(this, true, reg_off, vaddr, size, false); ++} ++ ++/* ++ * write_data_dma: prepares a DMA descriptor to transfer data from ++ * 'vaddr' to the controller's internal buffer ++ * ++ * @reg_off: offset within the controller's data buffer ++ * @vaddr: virtual address of the buffer we want to read from ++ * @size: DMA transaction size in bytes ++ */ ++static int write_data_dma(struct qcom_nandc_data *this, int reg_off, ++ const u8 *vaddr, int size) ++{ ++ return prep_dma_desc(this, false, reg_off, vaddr, size, false); ++} ++ ++/* ++ * helper to prepare dma descriptors to configure registers needed for reading a ++ * codeword/step in a page ++ */ ++static void config_cw_read(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 3); ++ write_reg_dma(this, NAND_EBI2_ECC_BUF_CFG, 1); ++ ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 2); ++ read_reg_dma(this, NAND_ERASED_CW_DETECT_STATUS, 1); ++} ++ ++/* ++ * helpers to prepare dma descriptors used to configure registers needed for ++ * writing a codeword/step in a page ++ */ ++static void config_cw_write_pre(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 3); ++ write_reg_dma(this, NAND_EBI2_ECC_BUF_CFG, 1); ++} ++ ++static void config_cw_write_post(struct qcom_nandc_data *this) ++{ ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ write_reg_dma(this, NAND_FLASH_STATUS, 1); ++ write_reg_dma(this, NAND_READ_STATUS, 1); ++} ++ ++/* ++ * the following functions are used within chip->cmdfunc() to perform different ++ * NAND_CMD_* commands ++ */ ++ ++/* sets up descriptors for NAND_CMD_PARAM */ ++static int nandc_param(struct qcom_nandc_data *this) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ /* ++ * NAND_CMD_PARAM is called before we know much about the FLASH chip ++ * in use. we configure the controller to perform a raw read of 512 ++ * bytes to read onfi params ++ */ ++ regs->cmd = PAGE_READ | PAGE_ACC | LAST_PAGE; ++ regs->addr0 = 0; ++ regs->addr1 = 0; ++ regs->cfg0 = 0 << CW_PER_PAGE ++ | 512 << UD_SIZE_BYTES ++ | 5 << NUM_ADDR_CYCLES ++ | 0 << SPARE_SIZE_BYTES; ++ ++ regs->cfg1 = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | 17 << BAD_BLOCK_BYTE_NUM ++ | 1 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | 0 << WIDE_FLASH ++ | 1 << DEV0_CFG1_ECC_DISABLE; ++ ++ regs->ecc_bch_cfg = 1 << ECC_CFG_ECC_DISABLE; ++ ++ /* configure CMD1 and VLD for ONFI param probing */ ++ regs->vld = (this->vld & ~(1 << READ_START_VLD)) ++ | 0 << READ_START_VLD; ++ ++ regs->cmd1 = (this->cmd1 & ~(0xFF << READ_ADDR)) ++ | NAND_CMD_PARAM << READ_ADDR; ++ ++ regs->exec = 1; ++ ++ regs->orig_cmd1 = this->cmd1; ++ regs->orig_vld = this->vld; ++ ++ write_reg_dma(this, NAND_DEV_CMD_VLD, 1); ++ write_reg_dma(this, NAND_DEV_CMD1, 1); ++ ++ this->buf_count = 512; ++ memset(this->data_buffer, 0xff, this->buf_count); ++ ++ config_cw_read(this); ++ ++ read_data_dma(this, FLASH_BUF_ACC, this->data_buffer, this->buf_count); ++ ++ /* restore CMD1 and VLD regs */ ++ write_reg_dma(this, NAND_DEV_CMD1_RESTORE, 1); ++ write_reg_dma(this, NAND_DEV_CMD_VLD_RESTORE, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_ERASE1 */ ++static int erase_block(struct qcom_nandc_data *this, int page_addr) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ regs->cmd = BLOCK_ERASE | PAGE_ACC | LAST_PAGE; ++ regs->addr0 = page_addr; ++ regs->addr1 = 0; ++ regs->cfg0 = this->cfg0_raw & ~(7 << CW_PER_PAGE); ++ regs->cfg1 = this->cfg1_raw; ++ regs->exec = 1; ++ regs->clrflashstatus = this->clrflashstatus; ++ regs->clrreadstatus = this->clrreadstatus; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 3); ++ write_reg_dma(this, NAND_DEV0_CFG0, 2); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ write_reg_dma(this, NAND_FLASH_STATUS, 1); ++ write_reg_dma(this, NAND_READ_STATUS, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_READID */ ++static int read_id(struct qcom_nandc_data *this, int column) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ if (column == -1) ++ return 0; ++ ++ regs->cmd = FETCH_ID; ++ regs->addr0 = column; ++ regs->addr1 = 0; ++ regs->chip_sel = DM_EN; ++ regs->exec = 1; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 4); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_READ_ID, 1); ++ ++ return 0; ++} ++ ++/* sets up descriptors for NAND_CMD_RESET */ ++static int reset(struct qcom_nandc_data *this) ++{ ++ struct nandc_regs *regs = this->regs; ++ ++ regs->cmd = RESET_DEVICE; ++ regs->exec = 1; ++ ++ write_reg_dma(this, NAND_FLASH_CMD, 1); ++ write_reg_dma(this, NAND_EXEC_CMD, 1); ++ ++ read_reg_dma(this, NAND_FLASH_STATUS, 1); ++ ++ return 0; ++} ++ ++/* helpers to submit/free our list of dma descriptors */ ++static void dma_callback(void *param) ++{ ++ struct qcom_nandc_data *this = param; ++ struct completion *c = &this->dma_done; ++ ++ complete(c); ++} ++ ++static int submit_descs(struct qcom_nandc_data *this) ++{ ++ struct completion *c = &this->dma_done; ++ struct desc_info *desc; ++ int r; ++ ++ init_completion(c); ++ ++ list_for_each_entry(desc, &this->list, list) { ++ /* ++ * we add a callback to the last descriptor in our list to ++ * notify completion of command ++ */ ++ if (list_is_last(&desc->list, &this->list)) { ++ desc->dma_desc->callback = dma_callback; ++ desc->dma_desc->callback_param = this; ++ } ++ ++ dmaengine_submit(desc->dma_desc); ++ } ++ ++ dma_async_issue_pending(this->chan); ++ ++ r = wait_for_completion_timeout(c, msecs_to_jiffies(500)); ++ if (!r) ++ return -ETIMEDOUT; ++ ++ return 0; ++} ++ ++static void free_descs(struct qcom_nandc_data *this) ++{ ++ struct desc_info *desc, *n; ++ ++ list_for_each_entry_safe(desc, n, &this->list, list) { ++ list_del(&desc->list); ++ dma_unmap_sg(this->dev, &desc->sgl, 1, desc->dir); ++ kfree(desc); ++ } ++} ++ ++/* reset the register read buffer for next NAND operation */ ++static void clear_read_regs(struct qcom_nandc_data *this) ++{ ++ this->reg_read_pos = 0; ++ memset(this->reg_read_buf, 0, MAX_REG_RD * sizeof(*this->reg_read_buf)); ++} ++ ++static void pre_command(struct qcom_nandc_data *this, int command) ++{ ++ this->buf_count = 0; ++ this->buf_start = 0; ++ this->use_ecc = false; ++ this->last_command = command; ++ ++ clear_read_regs(this); ++} ++ ++/* ++ * this is called after NAND_CMD_PAGEPROG and NAND_CMD_ERASE1 to set our ++ * privately maintained status byte, this status byte can be read after ++ * NAND_CMD_STATUS is called ++ */ ++static void parse_erase_write_errors(struct qcom_nandc_data *this, int command) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int num_cw; ++ int i; ++ ++ num_cw = command == NAND_CMD_PAGEPROG ? ecc->steps : 1; ++ ++ for (i = 0; i < num_cw; i++) { ++ __le32 flash_status = le32_to_cpu(this->reg_read_buf[i]); ++ ++ if (flash_status & FS_MPU_ERR) ++ this->status &= ~NAND_STATUS_WP; ++ ++ if (flash_status & FS_OP_ERR || (i == (num_cw - 1) && ++ (flash_status & FS_DEVICE_STS_ERR))) ++ this->status |= NAND_STATUS_FAIL; ++ } ++} ++ ++static void post_command(struct qcom_nandc_data *this, int command) ++{ ++ switch (command) { ++ case NAND_CMD_READID: ++ memcpy(this->data_buffer, this->reg_read_buf, this->buf_count); ++ break; ++ case NAND_CMD_PAGEPROG: ++ case NAND_CMD_ERASE1: ++ parse_erase_write_errors(this, command); ++ break; ++ default: ++ break; ++ } ++} ++ ++/* ++ * Implements chip->cmdfunc. It's only used for a limited set of commands. ++ * The rest of the commands wouldn't be called by upper layers. For example, ++ * NAND_CMD_READOOB would never be called because we have our own versions ++ * of read_oob ops for nand_ecc_ctrl. ++ */ ++static void qcom_nandc_command(struct mtd_info *mtd, unsigned int command, ++ int column, int page_addr) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ struct qcom_nandc_data *this = chip->priv; ++ bool wait = false; ++ int r = 0; ++ ++ pre_command(this, command); ++ ++ switch (command) { ++ case NAND_CMD_RESET: ++ r = reset(this); ++ wait = true; ++ break; ++ ++ case NAND_CMD_READID: ++ this->buf_count = 4; ++ r = read_id(this, column); ++ wait = true; ++ break; ++ ++ case NAND_CMD_PARAM: ++ r = nandc_param(this); ++ wait = true; ++ break; ++ ++ case NAND_CMD_ERASE1: ++ r = erase_block(this, page_addr); ++ wait = true; ++ break; ++ ++ case NAND_CMD_READ0: ++ /* we read the entire page for now */ ++ WARN_ON(column != 0); ++ ++ this->use_ecc = true; ++ set_address(this, 0, page_addr); ++ update_rw_regs(this, ecc->steps, true); ++ break; ++ ++ case NAND_CMD_SEQIN: ++ WARN_ON(column != 0); ++ set_address(this, 0, page_addr); ++ break; ++ ++ case NAND_CMD_PAGEPROG: ++ case NAND_CMD_STATUS: ++ case NAND_CMD_NONE: ++ default: ++ break; ++ } ++ ++ if (r) { ++ dev_err(this->dev, "failure executing command %d\n", ++ command); ++ free_descs(this); ++ return; ++ } ++ ++ if (wait) { ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, ++ "failure submitting descs for command %d\n", ++ command); ++ } ++ ++ free_descs(this); ++ ++ post_command(this, command); ++} ++ ++/* ++ * when using RS ECC, the NAND controller flags an error when reading an ++ * erased page. however, there are special characters at certain offsets when ++ * we read the erased page. we check here if the page is really empty. if so, ++ * we replace the magic characters with 0xffs ++ */ ++static bool empty_page_fixup(struct qcom_nandc_data *this, u8 *data_buf) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = ecc->steps; ++ u8 orig1[MAX_NUM_STEPS], orig2[MAX_NUM_STEPS]; ++ int i, j; ++ ++ /* if BCH is enabled, HW will take care of detecting erased pages */ ++ if (this->bch_enabled || !this->use_ecc) ++ return false; ++ ++ for (i = 0; i < cwperpage; i++) { ++ u8 *empty1, *empty2; ++ __le32 flash_status = le32_to_cpu(this->reg_read_buf[3 * i]); ++ ++ /* ++ * an erased page flags an error in NAND_FLASH_STATUS, check if ++ * the page is erased by looking for 0x54s at offsets 3 and 175 ++ * from the beginning of each codeword ++ */ ++ if (!(flash_status & FS_OP_ERR)) ++ break; ++ ++ empty1 = &data_buf[3 + i * this->cw_data]; ++ empty2 = &data_buf[175 + i * this->cw_data]; ++ ++ /* ++ * if the error wasn't because of an erased page, bail out and ++ * and let someone else do the error checking ++ */ ++ if ((*empty1 == 0x54 && *empty2 == 0xff) || ++ (*empty1 == 0xff && *empty2 == 0x54)) { ++ orig1[i] = *empty1; ++ orig2[i] = *empty2; ++ ++ *empty1 = 0xff; ++ *empty2 = 0xff; ++ } else { ++ break; ++ } ++ } ++ ++ if (i < cwperpage || memchr_inv(data_buf, 0xff, mtd->writesize)) ++ goto not_empty; ++ ++ /* ++ * tell the caller that the page was empty and is fixed up, so that ++ * parse_read_errors() doesn't think it's an error ++ */ ++ return true; ++ ++not_empty: ++ /* restore original values if not empty*/ ++ for (j = 0; j < i; j++) { ++ data_buf[3 + j * this->cw_data] = orig1[j]; ++ data_buf[175 + j * this->cw_data] = orig2[j]; ++ } ++ ++ return false; ++} ++ ++struct read_stats { ++ __le32 flash; ++ __le32 buffer; ++ __le32 erased_cw; ++}; ++ ++/* ++ * reads back status registers set by the controller to notify page read ++ * errors. this is equivalent to what 'ecc->correct()' would do. ++ */ ++static int parse_read_errors(struct qcom_nandc_data *this, bool erased_page) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = ecc->steps; ++ unsigned int max_bitflips = 0; ++ int i; ++ ++ for (i = 0; i < cwperpage; i++) { ++ int stat; ++ struct read_stats *buf; ++ ++ buf = (struct read_stats *) (this->reg_read_buf + 3 * i); ++ ++ buf->flash = le32_to_cpu(buf->flash); ++ buf->buffer = le32_to_cpu(buf->buffer); ++ buf->erased_cw = le32_to_cpu(buf->erased_cw); ++ ++ if (buf->flash & (FS_OP_ERR | FS_MPU_ERR)) { ++ ++ /* ignore erased codeword errors */ ++ if (this->bch_enabled) { ++ if ((buf->erased_cw & ERASED_CW) == ERASED_CW) ++ continue; ++ } else if (erased_page) { ++ continue; ++ } ++ ++ if (buf->buffer & BS_UNCORRECTABLE_BIT) { ++ mtd->ecc_stats.failed++; ++ continue; ++ } ++ } ++ ++ stat = buf->buffer & BS_CORRECTABLE_ERR_MSK; ++ mtd->ecc_stats.corrected += stat; ++ ++ max_bitflips = max_t(unsigned int, max_bitflips, stat); ++ } ++ ++ return max_bitflips; ++} ++ ++/* ++ * helper to perform the actual page read operation, used by ecc->read_page() ++ * and ecc->read_oob() ++ */ ++static int read_page_low(struct qcom_nandc_data *this, u8 *data_buf, ++ u8 *oob_buf) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int i, r; ++ ++ /* queue cmd descs for each codeword */ ++ for (i = 0; i < ecc->steps; i++) { ++ int data_size, oob_size; ++ ++ if (i == (ecc->steps - 1)) { ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ } else { ++ data_size = this->cw_data; ++ oob_size = ecc->bytes; ++ } ++ ++ config_cw_read(this); ++ ++ if (data_buf) ++ read_data_dma(this, FLASH_BUF_ACC, data_buf, data_size); ++ ++ if (oob_buf) ++ read_data_dma(this, FLASH_BUF_ACC + data_size, oob_buf, ++ oob_size); ++ ++ if (data_buf) ++ data_buf += data_size; ++ if (oob_buf) ++ oob_buf += oob_size; ++ } ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failure to read page/oob\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* ++ * a helper that copies the last step/codeword of a page (containing free oob) ++ * into our local buffer ++ */ ++static int copy_last_cw(struct qcom_nandc_data *this, bool use_ecc, int page) ++{ ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int size; ++ int r; ++ ++ clear_read_regs(this); ++ ++ size = use_ecc ? this->cw_data : this->cw_size; ++ ++ /* prepare a clean read buffer */ ++ memset(this->data_buffer, 0xff, size); ++ ++ this->use_ecc = use_ecc; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, true); ++ ++ config_cw_read(this); ++ ++ read_data_dma(this, FLASH_BUF_ACC, this->data_buffer, size); ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failed to copy last codeword\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* implements ecc->read_page() */ ++static int qcom_nandc_read_page(struct mtd_info *mtd, struct nand_chip *chip, ++ uint8_t *buf, int oob_required, int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ u8 *data_buf, *oob_buf = NULL; ++ bool erased_page; ++ int r; ++ ++ data_buf = buf; ++ oob_buf = oob_required ? chip->oob_poi : NULL; ++ ++ r = read_page_low(this, data_buf, oob_buf); ++ if (r) { ++ dev_err(this->dev, "failure to read page\n"); ++ return r; ++ } ++ ++ erased_page = empty_page_fixup(this, data_buf); ++ ++ return parse_read_errors(this, erased_page); ++} ++ ++/* implements ecc->read_oob() */ ++static int qcom_nandc_read_oob(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int r; ++ ++ clear_read_regs(this); ++ ++ this->use_ecc = true; ++ set_address(this, 0, page); ++ update_rw_regs(this, ecc->steps, true); ++ ++ r = read_page_low(this, NULL, chip->oob_poi); ++ if (r) ++ dev_err(this->dev, "failure to read oob\n"); ++ ++ return r; ++} ++ ++/* implements ecc->read_oob_raw(), used to read the bad block marker flag */ ++static int qcom_nandc_read_oob_raw(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int start, length; ++ int r; ++ ++ /* ++ * configure registers for a raw page read, the address is set to the ++ * beginning of the last codeword, we don't care about reading ecc ++ * portion of oob, just the free stuff ++ */ ++ r = copy_last_cw(this, false, page); ++ if (r) ++ return r; ++ ++ /* ++ * reading raw oob has 2 parts, first the bad block byte, then the ++ * actual free oob region. perform a memcpy in two steps ++ */ ++ start = mtd->writesize - (this->cw_size * (ecc->steps - 1)); ++ length = chip->options & NAND_BUSWIDTH_16 ? 2 : 1; ++ ++ memcpy(oob, this->data_buffer + start, length); ++ ++ oob += length; ++ ++ start = this->cw_data - (ecc->steps << 2) + 1; ++ length = ecc->steps << 2; ++ ++ memcpy(oob, this->data_buffer + start, length); ++ ++ return 0; ++} ++ ++/* implements ecc->write_page() */ ++static int qcom_nandc_write_page(struct mtd_info *mtd, struct nand_chip *chip, ++ const uint8_t *buf, int oob_required) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ u8 *data_buf, *oob_buf; ++ int i, r = 0; ++ ++ clear_read_regs(this); ++ ++ data_buf = (u8 *) buf; ++ oob_buf = chip->oob_poi; ++ ++ this->use_ecc = true; ++ update_rw_regs(this, ecc->steps, false); ++ ++ for (i = 0; i < ecc->steps; i++) { ++ int data_size, oob_size; ++ ++ if (i == (ecc->steps - 1)) { ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ } else { ++ data_size = this->cw_data; ++ oob_size = ecc->bytes; ++ } ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, data_buf, data_size); ++ ++ /* ++ * we don't really need to write anything to oob for the ++ * first n - 1 codewords since these oob regions just ++ * contain ecc that's written by the controller itself ++ */ ++ if (i == (ecc->steps - 1)) ++ write_data_dma(this, FLASH_BUF_ACC + data_size, ++ oob_buf, oob_size); ++ config_cw_write_post(this); ++ ++ data_buf += data_size; ++ oob_buf += oob_size; ++ } ++ ++ r = submit_descs(this); ++ if (r) ++ dev_err(this->dev, "failure to write page\n"); ++ ++ free_descs(this); ++ ++ return r; ++} ++ ++/* ++ * implements ecc->write_oob() ++ * ++ * the NAND controller cannot write only data or only oob within a codeword, ++ * since ecc is calculated for the combined codeword. we first copy the ++ * entire contents for the last codeword(data + oob), replace the old oob ++ * with the new one in chip->oob_poi, and then write the entire codeword. ++ * this read-copy-write operation results in a slight perormance loss. ++ */ ++static int qcom_nandc_write_oob(struct mtd_info *mtd, struct nand_chip *chip, ++ int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int free_boff; ++ int data_size, oob_size; ++ int r, status = 0; ++ ++ r = copy_last_cw(this, true, page); ++ if (r) ++ return r; ++ ++ clear_read_regs(this); ++ ++ /* calculate the data and oob size for the last codeword/step */ ++ data_size = ecc->size - ((ecc->steps - 1) << 2); ++ oob_size = (ecc->steps << 2) + ecc->bytes; ++ ++ /* ++ * the location of spare data in the oob buffer, we could also use ++ * ecc->layout.oobfree here ++ */ ++ free_boff = ecc->bytes * (ecc->steps - 1); ++ ++ /* override new oob content to last codeword */ ++ memcpy(this->data_buffer + data_size, oob + free_boff, oob_size); ++ ++ this->use_ecc = true; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, false); ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, this->data_buffer, ++ data_size + oob_size); ++ config_cw_write_post(this); ++ ++ r = submit_descs(this); ++ ++ free_descs(this); ++ ++ if (r) { ++ dev_err(this->dev, "failure to write oob\n"); ++ return -EIO; ++ } ++ ++ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ++ ++ status = chip->waitfunc(mtd, chip); ++ ++ return status & NAND_STATUS_FAIL ? -EIO : 0; ++} ++ ++/* implements ecc->write_oob_raw(), used to write bad block marker flag */ ++static int qcom_nandc_write_oob_raw(struct mtd_info *mtd, ++ struct nand_chip *chip, int page) ++{ ++ struct qcom_nandc_data *this = chip->priv; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ uint8_t *oob = chip->oob_poi; ++ int start, length; ++ int r, status = 0; ++ ++ r = copy_last_cw(this, false, page); ++ if (r) ++ return r; ++ ++ clear_read_regs(this); ++ ++ /* ++ * writing raw oob has 2 parts, first the bad block region, then the ++ * actual free region ++ */ ++ start = mtd->writesize - (this->cw_size * (ecc->steps - 1)); ++ length = chip->options & NAND_BUSWIDTH_16 ? 2 : 1; ++ ++ memcpy(this->data_buffer + start, oob, length); ++ ++ oob += length; ++ ++ start = this->cw_data - (ecc->steps << 2) + 1; ++ length = ecc->steps << 2; ++ ++ memcpy(this->data_buffer + start, oob, length); ++ ++ /* prepare write */ ++ this->use_ecc = false; ++ set_address(this, this->cw_size * (ecc->steps - 1), page); ++ update_rw_regs(this, 1, false); ++ ++ config_cw_write_pre(this); ++ write_data_dma(this, FLASH_BUF_ACC, this->data_buffer, this->cw_size); ++ config_cw_write_post(this); ++ ++ r = submit_descs(this); ++ ++ free_descs(this); ++ ++ if (r) { ++ dev_err(this->dev, "failure to write updated oob\n"); ++ return -EIO; ++ } ++ ++ chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1); ++ ++ status = chip->waitfunc(mtd, chip); ++ ++ return status & NAND_STATUS_FAIL ? -EIO : 0; ++} ++ ++/* ++ * the three functions below implement chip->read_byte(), chip->read_buf() ++ * and chip->write_buf() respectively. these aren't used for ++ * reading/writing page data, they are used for smaller data like reading ++ * id, status etc ++ */ ++static uint8_t qcom_nandc_read_byte(struct mtd_info *mtd) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ uint8_t *buf = this->data_buffer; ++ uint8_t ret = 0x0; ++ ++ if (this->last_command == NAND_CMD_STATUS) { ++ ret = this->status; ++ ++ this->status = NAND_STATUS_READY | NAND_STATUS_WP; ++ ++ return ret; ++ } ++ ++ if (this->buf_start < this->buf_count) ++ ret = buf[this->buf_start++]; ++ ++ return ret; ++} ++ ++static void qcom_nandc_read_buf(struct mtd_info *mtd, uint8_t *buf, int len) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ int real_len = min_t(size_t, len, this->buf_count - this->buf_start); ++ ++ memcpy(buf, this->data_buffer + this->buf_start, real_len); ++ this->buf_start += real_len; ++} ++ ++static void qcom_nandc_write_buf(struct mtd_info *mtd, const uint8_t *buf, ++ int len) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ int real_len = min_t(size_t, len, this->buf_count - this->buf_start); ++ ++ memcpy(this->data_buffer + this->buf_start, buf, real_len); ++ ++ this->buf_start += real_len; ++} ++ ++/* we support only one external chip for now */ ++static void qcom_nandc_select_chip(struct mtd_info *mtd, int chipnr) ++{ ++ struct nand_chip *chip = mtd->priv; ++ struct qcom_nandc_data *this = chip->priv; ++ ++ if (chipnr <= 0) ++ return; ++ ++ dev_warn(this->dev, "invalid chip select\n"); ++} ++ ++/* ++ * NAND controller page layout info ++ * ++ * |-----------------------| |---------------------------------| ++ * | xx.......xx| | *********xx.......xx| ++ * | DATA xx..ECC..xx| | DATA **SPARE**xx..ECC..xx| ++ * | (516) xx.......xx| | (516-n*4) **(n*4)**xx.......xx| ++ * | xx.......xx| | *********xx.......xx| ++ * |-----------------------| |---------------------------------| ++ * codeword 1,2..n-1 codeword n ++ * <---(528/532 Bytes)----> <-------(528/532 Bytes)----------> ++ * ++ * n = number of codewords in the page ++ * . = ECC bytes ++ * * = spare bytes ++ * x = unused/reserved bytes ++ * ++ * 2K page: n = 4, spare = 16 bytes ++ * 4K page: n = 8, spare = 32 bytes ++ * 8K page: n = 16, spare = 64 bytes ++ * ++ * the qcom nand controller operates at a sub page/codeword level. each ++ * codeword is 528 and 532 bytes for 4 bit and 8 bit ECC modes respectively. ++ * the number of ECC bytes vary based on the ECC strength and the bus width. ++ * ++ * the first n - 1 codewords contains 516 bytes of user data, the remaining ++ * 12/16 bytes consist of ECC and reserved data. The nth codeword contains ++ * both user data and spare(oobavail) bytes that sum up to 516 bytes. ++ * ++ * the layout described above is used by the controller when the ECC block is ++ * enabled. When we read a page with ECC enabled, the unused/reserved bytes are ++ * skipped and not copied to our internal buffer. therefore, the nand_ecclayout ++ * layouts defined below doesn't consider the positions occupied by the reserved ++ * bytes ++ * ++ * when the ECC block is disabled, one unused byte (or two for 16 bit bus width) ++ * in the last codeword is the position of bad block marker. the bad block ++ * marker cannot be accessed when ECC is enabled. ++ * ++ */ ++ ++/* ++ * Layouts for different page sizes and ecc modes. We skip the eccpos field ++ * since it isn't needed for this driver ++ */ ++ ++/* 2K page, 4 bit ECC */ ++static struct nand_ecclayout layout_oob_64 = { ++ .eccbytes = 40, ++ .oobfree = { ++ { 30, 16 }, ++ }, ++}; ++ ++/* 4K page, 4 bit ECC, 8/16 bit bus width */ ++static struct nand_ecclayout layout_oob_128 = { ++ .eccbytes = 80, ++ .oobfree = { ++ { 70, 32 }, ++ }, ++}; ++ ++/* 4K page, 8 bit ECC, 8 bit bus width */ ++static struct nand_ecclayout layout_oob_224_x8 = { ++ .eccbytes = 104, ++ .oobfree = { ++ { 91, 32 }, ++ }, ++}; ++ ++/* 4K page, 8 bit ECC, 16 bit bus width */ ++static struct nand_ecclayout layout_oob_224_x16 = { ++ .eccbytes = 112, ++ .oobfree = { ++ { 98, 32 }, ++ }, ++}; ++ ++/* 8K page, 4 bit ECC, 8/16 bit bus width */ ++static struct nand_ecclayout layout_oob_256 = { ++ .eccbytes = 160, ++ .oobfree = { ++ { 151, 64 }, ++ }, ++}; ++ ++/* ++ * this is called before scan_ident, we do some minimal configurations so ++ * that reading ID and ONFI params work ++ */ ++static void qcom_nandc_pre_init(struct qcom_nandc_data *this) ++{ ++ /* kill onenand */ ++ nandc_write(this, SFLASHC_BURST_CFG, 0); ++ ++ /* enable ADM DMA */ ++ nandc_write(this, NAND_FLASH_CHIP_SELECT, DM_EN); ++ ++ /* save the original values of these registers */ ++ this->cmd1 = nandc_read(this, NAND_DEV_CMD1); ++ this->vld = nandc_read(this, NAND_DEV_CMD_VLD); ++ ++ /* initial status value */ ++ this->status = NAND_STATUS_READY | NAND_STATUS_WP; ++} ++ ++static int qcom_nandc_ecc_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage; ++ bool wide_bus; ++ ++ /* the nand controller fetches codewords/chunks of 512 bytes */ ++ cwperpage = mtd->writesize >> 9; ++ ++ ecc->strength = this->ecc_strength; ++ ++ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; ++ ++ if (ecc->strength >= 8) { ++ /* 8 bit ECC defaults to BCH ECC on all platforms */ ++ ecc->bytes = wide_bus ? 14 : 13; ++ } else { ++ /* ++ * if the controller supports BCH for 4 bit ECC, the controller ++ * uses lesser bytes for ECC. If RS is used, the ECC bytes is ++ * always 10 bytes ++ */ ++ if (this->ecc_modes & ECC_BCH_4BIT) ++ ecc->bytes = wide_bus ? 8 : 7; ++ else ++ ecc->bytes = 10; ++ } ++ ++ /* each step consists of 512 bytes of data */ ++ ecc->size = NANDC_STEP_SIZE; ++ ++ ecc->read_page = qcom_nandc_read_page; ++ ecc->read_oob = qcom_nandc_read_oob; ++ ecc->write_page = qcom_nandc_write_page; ++ ecc->write_oob = qcom_nandc_write_oob; ++ ++ /* ++ * the bad block marker is readable only when we read the page with ECC ++ * disabled. all the ops above run with ECC enabled. We need raw read ++ * and write function for oob in order to access bad block marker. ++ */ ++ ecc->read_oob_raw = qcom_nandc_read_oob_raw; ++ ecc->write_oob_raw = qcom_nandc_write_oob_raw; ++ ++ switch (mtd->oobsize) { ++ case 64: ++ ecc->layout = &layout_oob_64; ++ break; ++ case 128: ++ ecc->layout = &layout_oob_128; ++ break; ++ case 224: ++ if (wide_bus) ++ ecc->layout = &layout_oob_224_x16; ++ else ++ ecc->layout = &layout_oob_224_x8; ++ break; ++ case 256: ++ ecc->layout = &layout_oob_256; ++ break; ++ default: ++ dev_err(this->dev, "unsupported NAND device, oobsize %d\n", ++ mtd->oobsize); ++ return -ENODEV; ++ } ++ ++ ecc->mode = NAND_ECC_HW; ++ ++ /* enable ecc by default */ ++ this->use_ecc = true; ++ ++ return 0; ++} ++ ++static void qcom_nandc_hw_post_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct nand_ecc_ctrl *ecc = &chip->ecc; ++ int cwperpage = mtd->writesize / ecc->size; ++ int spare_bytes, bad_block_byte; ++ bool wide_bus; ++ int ecc_mode = 0; ++ ++ wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; ++ ++ if (ecc->strength >= 8) { ++ this->cw_size = 532; ++ ++ spare_bytes = wide_bus ? 0 : 2; ++ ++ this->bch_enabled = true; ++ ecc_mode = 1; ++ } else { ++ this->cw_size = 528; ++ ++ if (this->ecc_modes & ECC_BCH_4BIT) { ++ spare_bytes = wide_bus ? 2 : 4; ++ ++ this->bch_enabled = true; ++ ecc_mode = 0; ++ } else { ++ spare_bytes = wide_bus ? 0 : 1; ++ } ++ } ++ ++ /* ++ * DATA_UD_BYTES varies based on whether the read/write command protects ++ * spare data with ECC too. We protect spare data by default, so we set ++ * it to main + spare data, which are 512 and 4 bytes respectively. ++ */ ++ this->cw_data = 516; ++ ++ bad_block_byte = mtd->writesize - this->cw_size * (cwperpage - 1) + 1; ++ ++ this->cfg0 = (cwperpage - 1) << CW_PER_PAGE ++ | this->cw_data << UD_SIZE_BYTES ++ | 0 << DISABLE_STATUS_AFTER_WRITE ++ | 5 << NUM_ADDR_CYCLES ++ | ecc->bytes << ECC_PARITY_SIZE_BYTES_RS ++ | 0 << STATUS_BFR_READ ++ | 1 << SET_RD_MODE_AFTER_STATUS ++ | spare_bytes << SPARE_SIZE_BYTES; ++ ++ this->cfg1 = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | bad_block_byte << BAD_BLOCK_BYTE_NUM ++ | 0 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | wide_bus << WIDE_FLASH ++ | this->bch_enabled << ENABLE_BCH_ECC; ++ ++ this->cfg0_raw = (cwperpage - 1) << CW_PER_PAGE ++ | this->cw_size << UD_SIZE_BYTES ++ | 5 << NUM_ADDR_CYCLES ++ | 0 << SPARE_SIZE_BYTES; ++ ++ this->cfg1_raw = 7 << NAND_RECOVERY_CYCLES ++ | 0 << CS_ACTIVE_BSY ++ | 17 << BAD_BLOCK_BYTE_NUM ++ | 1 << BAD_BLOCK_IN_SPARE_AREA ++ | 2 << WR_RD_BSY_GAP ++ | wide_bus << WIDE_FLASH ++ | 1 << DEV0_CFG1_ECC_DISABLE; ++ ++ this->ecc_bch_cfg = this->bch_enabled << ECC_CFG_ECC_DISABLE ++ | 0 << ECC_SW_RESET ++ | this->cw_data << ECC_NUM_DATA_BYTES ++ | 1 << ECC_FORCE_CLK_OPEN ++ | ecc_mode << ECC_MODE ++ | ecc->bytes << ECC_PARITY_SIZE_BYTES_BCH; ++ ++ this->ecc_buf_cfg = 0x203 << NUM_STEPS; ++ ++ this->clrflashstatus = FS_READY_BSY_N; ++ this->clrreadstatus = 0xc0; ++ ++ dev_dbg(this->dev, ++ "cfg0 %x cfg1 %x ecc_buf_cfg %x ecc_bch cfg %x cw_size %d cw_data %d strength %d parity_bytes %d steps %d\n", ++ this->cfg0, this->cfg1, this->ecc_buf_cfg, ++ this->ecc_bch_cfg, this->cw_size, this->cw_data, ++ ecc->strength, ecc->bytes, cwperpage); ++} ++ ++static int qcom_nandc_alloc(struct qcom_nandc_data *this) ++{ ++ int r; ++ ++ r = dma_set_coherent_mask(this->dev, DMA_BIT_MASK(32)); ++ if (r) { ++ dev_err(this->dev, "failed to set DMA mask\n"); ++ return r; ++ } ++ ++ /* ++ * we use the internal buffer for reading ONFI params, reading small ++ * data like ID and status, and preforming read-copy-write operations ++ * when writing to a codeword partially. 532 is the maximum possible ++ * size of a codeword for our nand controller ++ */ ++ this->buf_size = 532; ++ ++ this->data_buffer = devm_kzalloc(this->dev, this->buf_size, GFP_KERNEL); ++ if (!this->data_buffer) ++ return -ENOMEM; ++ ++ this->regs = devm_kzalloc(this->dev, sizeof(*this->regs), GFP_KERNEL); ++ if (!this->regs) ++ return -ENOMEM; ++ ++ this->reg_read_buf = devm_kzalloc(this->dev, ++ MAX_REG_RD * sizeof(*this->reg_read_buf), ++ GFP_KERNEL); ++ if (!this->reg_read_buf) ++ return -ENOMEM; ++ ++ INIT_LIST_HEAD(&this->list); ++ ++ this->chan = dma_request_slave_channel(this->dev, "rxtx"); ++ if (!this->chan) { ++ dev_err(this->dev, "failed to request slave channel\n"); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++static void qcom_nandc_unalloc(struct qcom_nandc_data *this) ++{ ++ dma_release_channel(this->chan); ++} ++ ++static int qcom_nandc_init(struct qcom_nandc_data *this) ++{ ++ struct mtd_info *mtd = &this->mtd; ++ struct nand_chip *chip = &this->chip; ++ struct device_node *np = this->dev->of_node; ++ struct mtd_part_parser_data ppdata = { .of_node = np }; ++ int r; ++ ++ mtd->priv = chip; ++ mtd->name = "qcom-nandc"; ++ mtd->owner = THIS_MODULE; ++ ++ chip->priv = this; ++ ++ chip->cmdfunc = qcom_nandc_command; ++ chip->select_chip = qcom_nandc_select_chip; ++ chip->read_byte = qcom_nandc_read_byte; ++ chip->read_buf = qcom_nandc_read_buf; ++ chip->write_buf = qcom_nandc_write_buf; ++ ++ chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USE_BOUNCE_BUFFER; ++ if (this->bus_width == 16) ++ chip->options |= NAND_BUSWIDTH_16; ++ ++ chip->bbt_options = NAND_BBT_ACCESS_BBM_RAW; ++ if (of_get_nand_on_flash_bbt(np)) ++ chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; ++ ++ qcom_nandc_pre_init(this); ++ ++ r = nand_scan_ident(mtd, 1, NULL); ++ if (r) ++ return r; ++ ++ r = qcom_nandc_ecc_init(this); ++ if (r) ++ return r; ++ ++ qcom_nandc_hw_post_init(this); ++ ++ r = nand_scan_tail(mtd); ++ if (r) ++ return r; ++ ++ return mtd_device_parse_register(mtd, NULL, &ppdata, NULL, 0); ++} ++ ++static int qcom_nandc_parse_dt(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this = platform_get_drvdata(pdev); ++ struct device_node *np = this->dev->of_node; ++ int r; ++ ++ this->ecc_strength = of_get_nand_ecc_strength(np); ++ if (this->ecc_strength < 0) { ++ dev_warn(this->dev, ++ "incorrect ecc strength, setting to 4 bits/step\n"); ++ this->ecc_strength = 4; ++ } ++ ++ this->bus_width = of_get_nand_bus_width(np); ++ if (this->bus_width < 0) { ++ dev_warn(this->dev, "incorrect bus width, setting to 8\n"); ++ this->bus_width = 8; ++ } ++ ++ r = of_property_read_u32(np, "qcom,cmd-crci", &this->cmd_crci); ++ if (r) { ++ dev_err(this->dev, "command CRCI unspecified\n"); ++ return r; ++ } ++ ++ r = of_property_read_u32(np, "qcom,data-crci", &this->data_crci); ++ if (r) { ++ dev_err(this->dev, "data CRCI unspecified\n"); ++ return r; ++ } ++ ++ return 0; ++} ++ ++static int qcom_nandc_probe(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this; ++ const struct of_device_id *match; ++ int r; ++ ++ this = devm_kzalloc(&pdev->dev, sizeof(*this), GFP_KERNEL); ++ if (!this) ++ return -ENOMEM; ++ ++ platform_set_drvdata(pdev, this); ++ ++ this->pdev = pdev; ++ this->dev = &pdev->dev; ++ ++ match = of_match_device(pdev->dev.driver->of_match_table, &pdev->dev); ++ if (!match) { ++ dev_err(&pdev->dev, "failed to match device\n"); ++ return -ENODEV; ++ } ++ ++ if (!match->data) { ++ dev_err(&pdev->dev, "failed to get device data\n"); ++ return -ENODEV; ++ } ++ ++ this->ecc_modes = (u32) match->data; ++ ++ this->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ this->base = devm_ioremap_resource(&pdev->dev, this->res); ++ if (IS_ERR(this->base)) ++ return PTR_ERR(this->base); ++ ++ this->core_clk = devm_clk_get(&pdev->dev, "core"); ++ if (IS_ERR(this->core_clk)) ++ return PTR_ERR(this->core_clk); ++ ++ this->aon_clk = devm_clk_get(&pdev->dev, "aon"); ++ if (IS_ERR(this->aon_clk)) ++ return PTR_ERR(this->aon_clk); ++ ++ r = qcom_nandc_parse_dt(pdev); ++ if (r) ++ return r; ++ ++ r = qcom_nandc_alloc(this); ++ if (r) ++ return r; ++ ++ r = clk_prepare_enable(this->core_clk); ++ if (r) ++ goto err_core_clk; ++ ++ r = clk_prepare_enable(this->aon_clk); ++ if (r) ++ goto err_aon_clk; ++ ++ r = qcom_nandc_init(this); ++ if (r) ++ goto err_init; ++ ++ return 0; ++ ++err_init: ++ clk_disable_unprepare(this->aon_clk); ++err_aon_clk: ++ clk_disable_unprepare(this->core_clk); ++err_core_clk: ++ qcom_nandc_unalloc(this); ++ ++ return r; ++} ++ ++static int qcom_nandc_remove(struct platform_device *pdev) ++{ ++ struct qcom_nandc_data *this = platform_get_drvdata(pdev); ++ ++ qcom_nandc_unalloc(this); ++ ++ clk_disable_unprepare(this->aon_clk); ++ clk_disable_unprepare(this->core_clk); ++ ++ return 0; ++} ++ ++#define EBI2_NANDC_ECC_MODES (ECC_RS_4BIT | ECC_BCH_8BIT) ++ ++/* ++ * data will hold a struct pointer containing more differences once we support ++ * more IPs ++ */ ++static const struct of_device_id qcom_nandc_of_match[] = { ++ { .compatible = "qcom,ebi2-nandc", ++ .data = (void *) EBI2_NANDC_ECC_MODES, ++ }, ++ {} ++}; ++MODULE_DEVICE_TABLE(of, qcom_nandc_of_match); ++ ++static struct platform_driver qcom_nandc_driver = { ++ .driver = { ++ .name = "qcom-nandc", ++ .of_match_table = qcom_nandc_of_match, ++ }, ++ .probe = qcom_nandc_probe, ++ .remove = qcom_nandc_remove, ++}; ++module_platform_driver(qcom_nandc_driver); ++ ++MODULE_AUTHOR("Archit Taneja "); ++MODULE_DESCRIPTION("Qualcomm NAND Controller driver"); ++MODULE_LICENSE("GPL v2"); +--- a/drivers/mtd/nand/Makefile ++++ b/drivers/mtd/nand/Makefile +@@ -52,5 +52,6 @@ obj-$(CONFIG_MTD_NAND_XWAY) += xway_nan + obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/ + obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o + obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o ++obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o + + nand-objs := nand_base.o nand_bbt.o nand_timings.o diff --git a/target/linux/ipq806x/patches-4.1/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch b/target/linux/ipq806x/patches-4.1/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch new file mode 100644 index 0000000..6530eb1 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/163-dt-bindings-qcom_nandc-Add-DT-bindings.patch @@ -0,0 +1,82 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,3/5] dt/bindings: qcom_nandc: Add DT bindings +From: Archit Taneja +X-Patchwork-Id: 6927141 +Message-Id: <1438578498-32254-4-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja , devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:16 +0530 + +Add DT bindings document for the Qualcomm NAND controller driver. + +Cc: devicetree@vger.kernel.org + +v3: +- Don't use '0x' when specifying nand controller address space +- Add optional property for on-flash bbt usage + +Acked-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +.../devicetree/bindings/mtd/qcom_nandc.txt | 49 ++++++++++++++++++++++ + 1 file changed, 49 insertions(+) + create mode 100644 Documentation/devicetree/bindings/mtd/qcom_nandc.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/mtd/qcom_nandc.txt +@@ -0,0 +1,49 @@ ++* Qualcomm NAND controller ++ ++Required properties: ++- compatible: should be "qcom,ebi2-nand" for IPQ806x ++- reg: MMIO address range ++- clocks: must contain core clock and always on clock ++- clock-names: must contain "core" for the core clock and "aon" for the ++ always on clock ++- dmas: DMA specifier, consisting of a phandle to the ADM DMA ++ controller node and the channel number to be used for ++ NAND. Refer to dma.txt and qcom_adm.txt for more details ++- dma-names: must be "rxtx" ++- qcom,cmd-crci: must contain the ADM command type CRCI block instance ++ number specified for the NAND controller on the given ++ platform ++- qcom,data-crci: must contain the ADM data type CRCI block instance ++ number specified for the NAND controller on the given ++ platform ++ ++Optional properties: ++- nand-bus-width: bus width. Must be 8 or 16. If not present, 8 is chosen ++ as default ++ ++- nand-ecc-strength: number of bits to correct per ECC step. Must be 4 or 8 ++ bits. If not present, 4 is chosen as default ++- nand-on-flash-bbt: Create/use on-flash bad block table ++ ++The device tree may optionally contain sub-nodes describing partitions of the ++address space. See partition.txt for more detail. ++ ++Example: ++ ++nand@1ac00000 { ++ compatible = "qcom,ebi2-nandc"; ++ reg = <0x1ac00000 0x800>; ++ ++ clocks = <&gcc EBI2_CLK>, ++ <&gcc EBI2_AON_CLK>; ++ clock-names = "core", "aon"; ++ ++ dmas = <&adm_dma 3>; ++ dma-names = "rxtx"; ++ qcom,cmd-crci = <15>; ++ qcom,data-crci = <3>; ++ ++ partition@0 { ++ ... ++ }; ++}; diff --git a/target/linux/ipq806x/patches-4.1/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch b/target/linux/ipq806x/patches-4.1/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch new file mode 100644 index 0000000..5049b60 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/164-arm-qcom-dts-Add-NAND-controller-node-for-ipq806x.patch @@ -0,0 +1,51 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,4/5] arm: qcom: dts: Add NAND controller node for ipq806x +From: Archit Taneja +X-Patchwork-Id: 6927121 +Message-Id: <1438578498-32254-5-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja , devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:17 +0530 + +The nand controller in IPQ806x is of the 'EBI2 type'. Use the corresponding +compatible string. + +Cc: devicetree@vger.kernel.org + +Reviewed-by: Andy Gross +Signed-off-by: Archit Taneja + +--- +arch/arm/boot/dts/qcom-ipq8064.dtsi | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -620,6 +620,22 @@ + + status = "disabled"; + }; ++ ++ nand@1ac00000 { ++ compatible = "qcom,ebi2-nandc"; ++ reg = <0x1ac00000 0x800>; ++ ++ clocks = <&gcc EBI2_CLK>, ++ <&gcc EBI2_AON_CLK>; ++ clock-names = "core", "aon"; ++ ++ dmas = <&adm_dma 3>; ++ dma-names = "rxtx"; ++ qcom,cmd-crci = <15>; ++ qcom,data-crci = <3>; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.1/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch b/target/linux/ipq806x/patches-4.1/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch new file mode 100644 index 0000000..415bf30 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/165-arm-qcom-dts-Enable-NAND-node-on-IPQ8064-AP148-platform.patch @@ -0,0 +1,76 @@ +Content-Type: text/plain; charset="utf-8" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Subject: [v3,5/5] arm: qcom: dts: Enable NAND node on IPQ8064 AP148 platform +From: Archit Taneja +X-Patchwork-Id: 6927091 +Message-Id: <1438578498-32254-6-git-send-email-architt@codeaurora.org> +To: linux-mtd@lists.infradead.org, dehrenberg@google.com, + cernekee@gmail.com, computersforpeace@gmail.com +Cc: linux-arm-msm@vger.kernel.org, agross@codeaurora.org, + sboyd@codeaurora.org, linux-kernel@vger.kernel.org, + Archit Taneja , devicetree@vger.kernel.org +Date: Mon, 3 Aug 2015 10:38:18 +0530 + +Enable the NAND controller node on the AP148 platform. Provide pinmux +information. + +Cc: devicetree@vger.kernel.org + +Signed-off-by: Archit Taneja + +--- +arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 36 ++++++++++++++++++++++++++++++++ + 1 file changed, 36 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -61,6 +61,28 @@ + bias-none; + }; + }; ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -123,5 +145,19 @@ + pinctrl-0 = <&pcie1_pins>; + pinctrl-names = "default"; + }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ }; + }; + }; ++ ++&adm_dma { ++ status = "ok"; ++}; diff --git a/target/linux/ipq806x/patches-4.1/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch b/target/linux/ipq806x/patches-4.1/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch new file mode 100644 index 0000000..16df418 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/166-arch-qcom-dts-enable-qcom-smem-on-AP148-NAND.patch @@ -0,0 +1,11 @@ +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -154,6 +154,8 @@ + + nand-ecc-strength = <4>; + nand-bus-width = <8>; ++ ++ linux,part-probe = "qcom-smem"; + }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.1/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch b/target/linux/ipq806x/patches-4.1/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch new file mode 100644 index 0000000..51ebc19 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/300-arch-arm-force-ZRELADDR-on-arch-qcom.patch @@ -0,0 +1,62 @@ +From b12e230f09d4481424e6a5d7e2ae566b6954e83f Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Wed, 29 Apr 2015 15:21:46 -0700 +Subject: [PATCH] HACK: arch: arm: force ZRELADDR on arch-qcom + +ARCH_QCOM is using the ARCH_MULTIPLATFORM option, as now recommended +on most ARM architectures. This automatically calculate ZRELADDR by +masking PHYS_OFFSET with 0xf8000000. + +However, on IPQ806x, the first ~20MB of RAM is reserved for the hardware +network accelerators, and the bootloader removes this section from the +layout passed from the ATAGS (when used). + +For newer bootloader, when DT is used, this is not a problem, we just +reserve this memory in the device tree. But if the bootloader doesn't +have DT support, then ATAGS have to be used. In this case, the ARM +decompressor will position the kernel in this low mem, which will not be +in the RAM section mapped by the bootloader, which means the kernel will +freeze in the middle of the boot process trying to map the memory. + +As a work around, this patch allows disabling AUTO_ZRELADDR when +ARCH_QCOM is selected. It makes the zImage usage possible on bootloaders +which don't support device-tree, which is the case on certain early +IPQ806x based designs. + +Signed-off-by: Mathieu Olivari +--- + arch/arm/Kconfig | 2 +- + arch/arm/Makefile | 2 ++ + arch/arm/mach-qcom/Makefile.boot | 1 + + 3 files changed, 4 insertions(+), 1 deletion(-) + create mode 100644 arch/arm/mach-qcom/Makefile.boot + +--- a/arch/arm/Kconfig ++++ b/arch/arm/Kconfig +@@ -320,7 +320,7 @@ config ARCH_MULTIPLATFORM + select ARCH_WANT_OPTIONAL_GPIOLIB + select ARM_HAS_SG_CHAIN + select ARM_PATCH_PHYS_VIRT +- select AUTO_ZRELADDR ++ select AUTO_ZRELADDR if !ARCH_QCOM + select CLKSRC_OF + select COMMON_CLK + select GENERIC_CLOCKEVENTS +--- a/arch/arm/Makefile ++++ b/arch/arm/Makefile +@@ -240,9 +240,11 @@ MACHINE := arch/arm/mach-$(word 1,$(mac + else + MACHINE := + endif ++ifeq ($(CONFIG_ARCH_QCOM),) + ifeq ($(CONFIG_ARCH_MULTIPLATFORM),y) + MACHINE := + endif ++endif + + machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y)) + platdirs := $(patsubst %,arch/arm/plat-%/,$(sort $(plat-y))) +--- /dev/null ++++ b/arch/arm/mach-qcom/Makefile.boot +@@ -0,0 +1 @@ ++zreladdr-y+= 0x42208000 diff --git a/target/linux/ipq806x/patches-4.1/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch b/target/linux/ipq806x/patches-4.1/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch new file mode 100644 index 0000000..12e06d2 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/301-ARM-qcom-add-Netgear-Nighthawk-X4-R7500-device-tree.patch @@ -0,0 +1,387 @@ +From 7e77aa188a7a7c4391856a9e5ef5ef58f769e679 Mon Sep 17 00:00:00 2001 +From: Jonas Gorski +Date: Sun, 9 Aug 2015 13:02:38 +0200 +Subject: [PATCH] ARM: qcom: add Netgear Nighthawk X4 R7500 device tree + +Signed-off-by: Jonas Gorski +--- + arch/arm/boot/dts/Makefile | 1 + + arch/arm/boot/dts/qcom-ipq8064-r7500.dts | 370 +++++++++++++++++++++++++++++++ + 2 files changed, 371 insertions(+) + create mode 100644 arch/arm/boot/dts/qcom-ipq8064-r7500.dts + +--- a/arch/arm/boot/dts/Makefile ++++ b/arch/arm/boot/dts/Makefile +@@ -452,6 +452,7 @@ dtb-$(CONFIG_ARCH_QCOM) += \ + qcom-apq8084-mtp.dtb \ + qcom-ipq8064-ap148.dtb \ + qcom-ipq8064-db149.dtb \ ++ qcom-ipq8064-r7500.dtb \ + qcom-msm8660-surf.dtb \ + qcom-msm8960-cdp.dtb \ + qcom-msm8974-sony-xperia-honami.dtb +--- /dev/null ++++ b/arch/arm/boot/dts/qcom-ipq8064-r7500.dts +@@ -0,0 +1,362 @@ ++#include "qcom-ipq8064-v1.0.dtsi" ++ ++#include ++ ++/ { ++ model = "Netgear Nighthawk X4 R7500"; ++ compatible = "netgear,r7500", "qcom,ipq8064"; ++ ++ memory@0 { ++ reg = <0x42000000 0xe000000>; ++ device_type = "memory"; ++ }; ++ ++ reserved-memory { ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ranges; ++ rsvd@41200000 { ++ reg = <0x41200000 0x300000>; ++ no-map; ++ }; ++ }; ++ ++ aliases { ++ serial0 = &uart4; ++ mdio-gpio0 = &mdio0; ++ }; ++ ++ chosen { ++ bootargs = "rootfstype=squashfs noinitrd"; ++ linux,stdout-path = "serial0:115200n8"; ++ }; ++ ++ soc { ++ pinmux@800000 { ++ i2c4_pins: i2c4_pinmux { ++ pins = "gpio12", "gpio13"; ++ function = "gsbi4"; ++ bias-disable; ++ }; ++ ++ pcie0_pins: pcie0_pinmux { ++ mux { ++ pins = "gpio3"; ++ function = "pcie1_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ pcie1_pins: pcie1_pinmux { ++ mux { ++ pins = "gpio48"; ++ function = "pcie2_rst"; ++ drive-strength = <12>; ++ bias-disable; ++ }; ++ }; ++ ++ nand_pins: nand_pins { ++ mux { ++ pins = "gpio34", "gpio35", "gpio36", ++ "gpio37", "gpio38", "gpio39", ++ "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ function = "nand"; ++ drive-strength = <10>; ++ bias-disable; ++ }; ++ pullups { ++ pins = "gpio39"; ++ bias-pull-up; ++ }; ++ hold { ++ pins = "gpio40", "gpio41", "gpio42", ++ "gpio43", "gpio44", "gpio45", ++ "gpio46", "gpio47"; ++ bias-bus-hold; ++ }; ++ }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; ++ }; ++ ++ gsbi@16300000 { ++ qcom,mode = ; ++ status = "ok"; ++ serial@16340000 { ++ status = "ok"; ++ }; ++ /* ++ * The i2c device on gsbi4 should not be enabled. ++ * On ipq806x designs gsbi4 i2c is meant for exclusive ++ * RPM usage. Turning this on in kernel manifests as ++ * i2c failure for the RPM. ++ */ ++ }; ++ ++ sata-phy@1b400000 { ++ status = "ok"; ++ }; ++ ++ sata@29000000 { ++ status = "ok"; ++ }; ++ ++ phy@100f8800 { /* USB3 port 1 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@100f8830 { /* USB3 port 1 SS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8800 { /* USB3 port 0 HS phy */ ++ status = "ok"; ++ }; ++ ++ phy@110f8830 { /* USB3 port 0 SS phy */ ++ status = "ok"; ++ }; ++ ++ usb30@0 { ++ status = "ok"; ++ }; ++ ++ usb30@1 { ++ status = "ok"; ++ }; ++ ++ pcie0: pci@1b500000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 3 0>; ++ pinctrl-0 = <&pcie0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ pcie1: pci@1b700000 { ++ status = "ok"; ++ reset-gpio = <&qcom_pinmux 48 0>; ++ pinctrl-0 = <&pcie1_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ nand@1ac00000 { ++ status = "ok"; ++ ++ pinctrl-0 = <&nand_pins>; ++ pinctrl-names = "default"; ++ ++ nand-ecc-strength = <4>; ++ nand-bus-width = <8>; ++ ++ #address-cells = <1>; ++ #size-cells = <1>; ++ ++ qcadata@0 { ++ label = "qcadata"; ++ reg = <0x0000000 0x0c80000>; ++ read-only; ++ }; ++ ++ APPSBL@c80000 { ++ label = "APPSBL"; ++ reg = <0x0c80000 0x0500000>; ++ read-only; ++ }; ++ ++ APPSBLENV@1180000 { ++ label = "APPSBLENV"; ++ reg = <0x1180000 0x0080000>; ++ read-only; ++ }; ++ ++ art: art@1200000 { ++ label = "art"; ++ reg = <0x1200000 0x0140000>; ++ read-only; ++ }; ++ ++ kernel@1340000 { ++ label = "kernel"; ++ reg = <0x1340000 0x0200000>; ++ }; ++ ++ ubi@1540000 { ++ label = "ubi"; ++ reg = <0x1540000 0x1800000>; ++ }; ++ ++ netgear@2d40000 { ++ label = "netgear"; ++ reg = <0x2d40000 0x0c00000>; ++ read-only; ++ }; ++ ++ reserve@3940000 { ++ label = "reserve"; ++ reg = <0x3940000 0x46c0000>; ++ read-only; ++ }; ++ ++ firmware@1340000 { ++ label = "firmware"; ++ reg = <0x1340000 0x1a00000>; ++ }; ++ ++ }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ phy-handle = <&phy4>; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ ++ mtd-mac-address = <&art 6>; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ mtd-mac-address = <&art 0>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ }; ++ ++ gpio-keys { ++ compatible = "gpio-keys"; ++ ++ wifi { ++ label = "wifi"; ++ gpios = <&qcom_pinmux 6 1>; ++ linux,code = ; ++ }; ++ ++ reset { ++ label = "reset"; ++ gpios = <&qcom_pinmux 54 1>; ++ linux,code = ; ++ }; ++ ++ wps { ++ label = "wps"; ++ gpios = <&qcom_pinmux 65 1>; ++ linux,code = ; ++ }; ++ }; ++ ++ gpio-leds { ++ compatible = "gpio-leds"; ++ ++ usb1 { ++ label = "r7500:amber:usb1"; ++ gpios = <&qcom_pinmux 7 0>; ++ }; ++ ++ usb3 { ++ label = "r7500:amber:usb3"; ++ gpios = <&qcom_pinmux 8 0>; ++ }; ++ ++ status { ++ label = "r7500:amber:status"; ++ gpios = <&qcom_pinmux 9 0>; ++ }; ++ ++ internet { ++ label = "r7500:white:internet"; ++ gpios = <&qcom_pinmux 22 0>; ++ }; ++ ++ wan { ++ label = "r7500:white:wan"; ++ gpios = <&qcom_pinmux 23 0>; ++ }; ++ ++ wps { ++ label = "r7500:white:wps"; ++ gpios = <&qcom_pinmux 24 0>; ++ }; ++ ++ esata { ++ label = "r7500:white:esata"; ++ gpios = <&qcom_pinmux 26 0>; ++ }; ++ ++ power { ++ label = "r7500:white:power"; ++ gpios = <&qcom_pinmux 53 0>; ++ default-state = "on"; ++ }; ++ ++ rfkill { ++ label = "r7500:white:rfkill"; ++ gpios = <&qcom_pinmux 64 0>; ++ }; ++ ++ wifi5g { ++ label = "r7500:white:wifi5g"; ++ gpios = <&qcom_pinmux 67 0>; ++ }; ++ }; ++}; ++ ++&adm_dma { ++ status = "ok"; ++}; diff --git a/target/linux/ipq806x/patches-4.1/302-mtd-qcom-smem-rename-rootfs-ubi.patch b/target/linux/ipq806x/patches-4.1/302-mtd-qcom-smem-rename-rootfs-ubi.patch new file mode 100644 index 0000000..471a87b --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/302-mtd-qcom-smem-rename-rootfs-ubi.patch @@ -0,0 +1,13 @@ +--- a/drivers/mtd/qcom_smem_part.c ++++ b/drivers/mtd/qcom_smem_part.c +@@ -192,6 +192,10 @@ static int parse_qcom_smem_partitions(st + m_part->size = le32_to_cpu(s_part->size) * (*smem_blksz); + m_part->offset = le32_to_cpu(s_part->start) * (*smem_blksz); + ++ /* "rootfs" conflicts with OpenWrt auto mounting */ ++ if (mtd_type_is_nand(master) && !strcmp(m_part->name, "rootfs")) ++ m_part->name = "ubi"; ++ + /* + * The last SMEM partition may have its size marked as + * something like 0xffffffff, which means "until the end of the diff --git a/target/linux/ipq806x/patches-4.1/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch b/target/linux/ipq806x/patches-4.1/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch new file mode 100644 index 0000000..dfe7169 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/700-clk-qcom-Add-support-for-NSS-GMAC-clocks-and-resets.patch @@ -0,0 +1,734 @@ +From 2fbb18f85826a9ba308fedb2cf90d3a661a39fd7 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd +Date: Fri, 27 Mar 2015 00:16:14 -0700 +Subject: [PATCH] clk: qcom: Add support for NSS/GMAC clocks and resets + +Add the NSS/GMAC clocks and the TCM clock and NSS resets. + +Signed-off-by: Stephen Boyd +--- + drivers/clk/qcom/gcc-ipq806x.c | 594 ++++++++++++++++++++++++++- + drivers/clk/qcom/gcc-ipq806x.c.rej | 50 +++ + include/dt-bindings/clock/qcom,gcc-ipq806x.h | 2 + + include/dt-bindings/reset/qcom,gcc-ipq806x.h | 43 ++ + 4 files changed, 688 insertions(+), 1 deletion(-) + create mode 100644 drivers/clk/qcom/gcc-ipq806x.c.rej + +--- a/drivers/clk/qcom/gcc-ipq806x.c ++++ b/drivers/clk/qcom/gcc-ipq806x.c +@@ -220,12 +220,47 @@ static struct clk_regmap pll14_vote = { + }, + }; + ++#define NSS_PLL_RATE(f, _l, _m, _n, i) \ ++ { \ ++ .freq = f, \ ++ .l = _l, \ ++ .m = _m, \ ++ .n = _n, \ ++ .ibits = i, \ ++ } ++ ++static struct pll_freq_tbl pll18_freq_tbl[] = { ++ NSS_PLL_RATE(550000000, 44, 0, 1, 0x01495625), ++ NSS_PLL_RATE(733000000, 58, 16, 25, 0x014b5625), ++}; ++ ++static struct clk_pll pll18 = { ++ .l_reg = 0x31a4, ++ .m_reg = 0x31a8, ++ .n_reg = 0x31ac, ++ .config_reg = 0x31b4, ++ .mode_reg = 0x31a0, ++ .status_reg = 0x31b8, ++ .status_bit = 16, ++ .post_div_shift = 16, ++ .post_div_width = 1, ++ .freq_tbl = pll18_freq_tbl, ++ .clkr.hw.init = &(struct clk_init_data){ ++ .name = "pll18", ++ .parent_names = (const char *[]){ "pxo" }, ++ .num_parents = 1, ++ .ops = &clk_pll_ops, ++ }, ++}; ++ + enum { + P_PXO, + P_PLL8, + P_PLL3, + P_PLL0, + P_CXO, ++ P_PLL14, ++ P_PLL18, + }; + + static const struct parent_map gcc_pxo_pll8_map[] = { +@@ -277,6 +312,22 @@ static const char *gcc_pxo_pll8_pll0_map + "pll0_vote", + }; + ++static const struct parent_map gcc_pxo_pll8_pll14_pll18_pll0_map[] = { ++ { P_PXO, 0 }, ++ { P_PLL8, 4 }, ++ { P_PLL0, 2 }, ++ { P_PLL14, 5 }, ++ { P_PLL18, 1 }, ++}; ++ ++static const char *gcc_pxo_pll8_pll14_pll18_pll0[] = { ++ "pxo", ++ "pll8_vote", ++ "pll0_vote", ++ "pll14", ++ "pll18", ++}; ++ + static struct freq_tbl clk_tbl_gsbi_uart[] = { + { 1843200, P_PLL8, 2, 6, 625 }, + { 3686400, P_PLL8, 2, 12, 625 }, +@@ -2282,6 +2333,472 @@ static struct clk_branch ebi2_aon_clk = + }, + }; + ++static const struct freq_tbl clk_tbl_gmac[] = { ++ { 133000000, P_PLL0, 1, 50, 301 }, ++ { 266000000, P_PLL0, 1, 127, 382 }, ++ { } ++}; ++ ++static struct clk_dyn_rcg gmac_core1_src = { ++ .ns_reg[0] = 0x3cac, ++ .ns_reg[1] = 0x3cb0, ++ .md_reg[0] = 0x3ca4, ++ .md_reg[1] = 0x3ca8, ++ .bank_reg = 0x3ca0, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3ca0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core1_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core1_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 4, ++ .hwcg_reg = 0x3cb4, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3cb4, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core1_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core1_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg gmac_core2_src = { ++ .ns_reg[0] = 0x3ccc, ++ .ns_reg[1] = 0x3cd0, ++ .md_reg[0] = 0x3cc4, ++ .md_reg[1] = 0x3cc8, ++ .bank_reg = 0x3ca0, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3cc0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core2_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core2_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 5, ++ .hwcg_reg = 0x3cd4, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3cd4, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core2_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core2_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg gmac_core3_src = { ++ .ns_reg[0] = 0x3cec, ++ .ns_reg[1] = 0x3cf0, ++ .md_reg[0] = 0x3ce4, ++ .md_reg[1] = 0x3ce8, ++ .bank_reg = 0x3ce0, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3ce0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core3_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core3_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 6, ++ .hwcg_reg = 0x3cf4, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3cf4, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core3_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core3_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg gmac_core4_src = { ++ .ns_reg[0] = 0x3d0c, ++ .ns_reg[1] = 0x3d10, ++ .md_reg[0] = 0x3d04, ++ .md_reg[1] = 0x3d08, ++ .bank_reg = 0x3d00, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_gmac, ++ .clkr = { ++ .enable_reg = 0x3d00, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core4_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch gmac_core4_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 7, ++ .hwcg_reg = 0x3d14, ++ .hwcg_bit = 6, ++ .clkr = { ++ .enable_reg = 0x3d14, ++ .enable_mask = BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "gmac_core4_clk", ++ .parent_names = (const char *[]){ ++ "gmac_core4_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static const struct freq_tbl clk_tbl_nss_tcm[] = { ++ { 266000000, P_PLL0, 3, 0, 0 }, ++ { 400000000, P_PLL0, 2, 0, 0 }, ++ { } ++}; ++ ++static struct clk_dyn_rcg nss_tcm_src = { ++ .ns_reg[0] = 0x3dc4, ++ .ns_reg[1] = 0x3dc8, ++ .bank_reg = 0x3dc0, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 4, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 4, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_nss_tcm, ++ .clkr = { ++ .enable_reg = 0x3dc0, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "nss_tcm_src", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ }, ++ }, ++}; ++ ++static struct clk_branch nss_tcm_clk = { ++ .halt_reg = 0x3c20, ++ .halt_bit = 14, ++ .clkr = { ++ .enable_reg = 0x3dd0, ++ .enable_mask = BIT(6) | BIT(4), ++ .hw.init = &(struct clk_init_data){ ++ .name = "nss_tcm_clk", ++ .parent_names = (const char *[]){ ++ "nss_tcm_src", ++ }, ++ .num_parents = 1, ++ .ops = &clk_branch_ops, ++ .flags = CLK_SET_RATE_PARENT, ++ }, ++ }, ++}; ++ ++static const struct freq_tbl clk_tbl_nss[] = { ++ { 110000000, P_PLL18, 1, 1, 5 }, ++ { 275000000, P_PLL18, 2, 0, 0 }, ++ { 550000000, P_PLL18, 1, 0, 0 }, ++ { 733000000, P_PLL18, 1, 0, 0 }, ++ { } ++}; ++ ++static struct clk_dyn_rcg ubi32_core1_src_clk = { ++ .ns_reg[0] = 0x3d2c, ++ .ns_reg[1] = 0x3d30, ++ .md_reg[0] = 0x3d24, ++ .md_reg[1] = 0x3d28, ++ .bank_reg = 0x3d20, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_nss, ++ .clkr = { ++ .enable_reg = 0x3d20, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "ubi32_core1_src_clk", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, ++ }, ++ }, ++}; ++ ++static struct clk_dyn_rcg ubi32_core2_src_clk = { ++ .ns_reg[0] = 0x3d4c, ++ .ns_reg[1] = 0x3d50, ++ .md_reg[0] = 0x3d44, ++ .md_reg[1] = 0x3d48, ++ .bank_reg = 0x3d40, ++ .mn[0] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .mn[1] = { ++ .mnctr_en_bit = 8, ++ .mnctr_reset_bit = 7, ++ .mnctr_mode_shift = 5, ++ .n_val_shift = 16, ++ .m_val_shift = 16, ++ .width = 8, ++ }, ++ .s[0] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .s[1] = { ++ .src_sel_shift = 0, ++ .parent_map = gcc_pxo_pll8_pll14_pll18_pll0_map, ++ }, ++ .p[0] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .p[1] = { ++ .pre_div_shift = 3, ++ .pre_div_width = 2, ++ }, ++ .mux_sel_bit = 0, ++ .freq_tbl = clk_tbl_nss, ++ .clkr = { ++ .enable_reg = 0x3d40, ++ .enable_mask = BIT(1), ++ .hw.init = &(struct clk_init_data){ ++ .name = "ubi32_core2_src_clk", ++ .parent_names = gcc_pxo_pll8_pll14_pll18_pll0, ++ .num_parents = 5, ++ .ops = &clk_dyn_rcg_ops, ++ .flags = CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE, ++ }, ++ }, ++}; ++ + static struct clk_regmap *gcc_ipq806x_clks[] = { + [PLL0] = &pll0.clkr, + [PLL0_VOTE] = &pll0_vote, +@@ -2291,6 +2808,7 @@ static struct clk_regmap *gcc_ipq806x_cl + [PLL8_VOTE] = &pll8_vote, + [PLL14] = &pll14.clkr, + [PLL14_VOTE] = &pll14_vote, ++ [PLL18] = &pll18.clkr, + [GSBI1_UART_SRC] = &gsbi1_uart_src.clkr, + [GSBI1_UART_CLK] = &gsbi1_uart_clk.clkr, + [GSBI2_UART_SRC] = &gsbi2_uart_src.clkr, +@@ -2390,6 +2908,18 @@ static struct clk_regmap *gcc_ipq806x_cl + [PLL9] = &hfpll0.clkr, + [PLL10] = &hfpll1.clkr, + [PLL12] = &hfpll_l2.clkr, ++ [GMAC_CORE1_CLK_SRC] = &gmac_core1_src.clkr, ++ [GMAC_CORE1_CLK] = &gmac_core1_clk.clkr, ++ [GMAC_CORE2_CLK_SRC] = &gmac_core2_src.clkr, ++ [GMAC_CORE2_CLK] = &gmac_core2_clk.clkr, ++ [GMAC_CORE3_CLK_SRC] = &gmac_core3_src.clkr, ++ [GMAC_CORE3_CLK] = &gmac_core3_clk.clkr, ++ [GMAC_CORE4_CLK_SRC] = &gmac_core4_src.clkr, ++ [GMAC_CORE4_CLK] = &gmac_core4_clk.clkr, ++ [UBI32_CORE1_CLK_SRC] = &ubi32_core1_src_clk.clkr, ++ [UBI32_CORE2_CLK_SRC] = &ubi32_core2_src_clk.clkr, ++ [NSSTCM_CLK_SRC] = &nss_tcm_src.clkr, ++ [NSSTCM_CLK] = &nss_tcm_clk.clkr, + }; + + static const struct qcom_reset_map gcc_ipq806x_resets[] = { +@@ -2508,6 +3038,48 @@ static const struct qcom_reset_map gcc_i + [USB30_1_PHY_RESET] = { 0x3b58, 0 }, + [NSSFB0_RESET] = { 0x3b60, 6 }, + [NSSFB1_RESET] = { 0x3b60, 7 }, ++ [UBI32_CORE1_CLKRST_CLAMP_RESET] = { 0x3d3c, 3}, ++ [UBI32_CORE1_CLAMP_RESET] = { 0x3d3c, 2 }, ++ [UBI32_CORE1_AHB_RESET] = { 0x3d3c, 1 }, ++ [UBI32_CORE1_AXI_RESET] = { 0x3d3c, 0 }, ++ [UBI32_CORE2_CLKRST_CLAMP_RESET] = { 0x3d5c, 3 }, ++ [UBI32_CORE2_CLAMP_RESET] = { 0x3d5c, 2 }, ++ [UBI32_CORE2_AHB_RESET] = { 0x3d5c, 1 }, ++ [UBI32_CORE2_AXI_RESET] = { 0x3d5c, 0 }, ++ [GMAC_CORE1_RESET] = { 0x3cbc, 0 }, ++ [GMAC_CORE2_RESET] = { 0x3cdc, 0 }, ++ [GMAC_CORE3_RESET] = { 0x3cfc, 0 }, ++ [GMAC_CORE4_RESET] = { 0x3d1c, 0 }, ++ [GMAC_AHB_RESET] = { 0x3e24, 0 }, ++ [NSS_CH0_RST_RX_CLK_N_RESET] = { 0x3b60, 0 }, ++ [NSS_CH0_RST_TX_CLK_N_RESET] = { 0x3b60, 1 }, ++ [NSS_CH0_RST_RX_125M_N_RESET] = { 0x3b60, 2 }, ++ [NSS_CH0_HW_RST_RX_125M_N_RESET] = { 0x3b60, 3 }, ++ [NSS_CH0_RST_TX_125M_N_RESET] = { 0x3b60, 4 }, ++ [NSS_CH1_RST_RX_CLK_N_RESET] = { 0x3b60, 5 }, ++ [NSS_CH1_RST_TX_CLK_N_RESET] = { 0x3b60, 6 }, ++ [NSS_CH1_RST_RX_125M_N_RESET] = { 0x3b60, 7 }, ++ [NSS_CH1_HW_RST_RX_125M_N_RESET] = { 0x3b60, 8 }, ++ [NSS_CH1_RST_TX_125M_N_RESET] = { 0x3b60, 9 }, ++ [NSS_CH2_RST_RX_CLK_N_RESET] = { 0x3b60, 10 }, ++ [NSS_CH2_RST_TX_CLK_N_RESET] = { 0x3b60, 11 }, ++ [NSS_CH2_RST_RX_125M_N_RESET] = { 0x3b60, 12 }, ++ [NSS_CH2_HW_RST_RX_125M_N_RESET] = { 0x3b60, 13 }, ++ [NSS_CH2_RST_TX_125M_N_RESET] = { 0x3b60, 14 }, ++ [NSS_CH3_RST_RX_CLK_N_RESET] = { 0x3b60, 15 }, ++ [NSS_CH3_RST_TX_CLK_N_RESET] = { 0x3b60, 16 }, ++ [NSS_CH3_RST_RX_125M_N_RESET] = { 0x3b60, 17 }, ++ [NSS_CH3_HW_RST_RX_125M_N_RESET] = { 0x3b60, 18 }, ++ [NSS_CH3_RST_TX_125M_N_RESET] = { 0x3b60, 19 }, ++ [NSS_RST_RX_250M_125M_N_RESET] = { 0x3b60, 20 }, ++ [NSS_RST_TX_250M_125M_N_RESET] = { 0x3b60, 21 }, ++ [NSS_QSGMII_TXPI_RST_N_RESET] = { 0x3b60, 22 }, ++ [NSS_QSGMII_CDR_RST_N_RESET] = { 0x3b60, 23 }, ++ [NSS_SGMII2_CDR_RST_N_RESET] = { 0x3b60, 24 }, ++ [NSS_SGMII3_CDR_RST_N_RESET] = { 0x3b60, 25 }, ++ [NSS_CAL_PRBS_RST_N_RESET] = { 0x3b60, 26 }, ++ [NSS_LCKDT_RST_N_RESET] = { 0x3b60, 27 }, ++ [NSS_SRDS_N_RESET] = { 0x3b60, 28 }, + }; + + static const struct regmap_config gcc_ipq806x_regmap_config = { +@@ -2536,6 +3108,8 @@ static int gcc_ipq806x_probe(struct plat + { + struct clk *clk; + struct device *dev = &pdev->dev; ++ struct regmap *regmap; ++ int ret; + + /* Temporary until RPM clocks supported */ + clk = clk_register_fixed_rate(dev, "cxo", NULL, CLK_IS_ROOT, 25000000); +@@ -2546,7 +3120,25 @@ static int gcc_ipq806x_probe(struct plat + if (IS_ERR(clk)) + return PTR_ERR(clk); + +- return qcom_cc_probe(pdev, &gcc_ipq806x_desc); ++ ret = qcom_cc_probe(pdev, &gcc_ipq806x_desc); ++ if (ret) ++ return ret; ++ ++ regmap = dev_get_regmap(dev, NULL); ++ if (!regmap) ++ return -ENODEV; ++ ++ /* Setup PLL18 static bits */ ++ regmap_update_bits(regmap, 0x31a4, 0xffffffc0, 0x40000400); ++ regmap_write(regmap, 0x31b0, 0x3080); ++ ++ /* Set GMAC footswitch sleep/wakeup values */ ++ regmap_write(regmap, 0x3cb8, 8); ++ regmap_write(regmap, 0x3cd8, 8); ++ regmap_write(regmap, 0x3cf8, 8); ++ regmap_write(regmap, 0x3d18, 8); ++ ++ return 0; + } + + static int gcc_ipq806x_remove(struct platform_device *pdev) +--- a/include/dt-bindings/reset/qcom,gcc-ipq806x.h ++++ b/include/dt-bindings/reset/qcom,gcc-ipq806x.h +@@ -129,4 +129,47 @@ + #define USB30_1_PHY_RESET 112 + #define NSSFB0_RESET 113 + #define NSSFB1_RESET 114 ++#define UBI32_CORE1_CLKRST_CLAMP_RESET 115 ++#define UBI32_CORE1_CLAMP_RESET 116 ++#define UBI32_CORE1_AHB_RESET 117 ++#define UBI32_CORE1_AXI_RESET 118 ++#define UBI32_CORE2_CLKRST_CLAMP_RESET 119 ++#define UBI32_CORE2_CLAMP_RESET 120 ++#define UBI32_CORE2_AHB_RESET 121 ++#define UBI32_CORE2_AXI_RESET 122 ++#define GMAC_CORE1_RESET 123 ++#define GMAC_CORE2_RESET 124 ++#define GMAC_CORE3_RESET 125 ++#define GMAC_CORE4_RESET 126 ++#define GMAC_AHB_RESET 127 ++#define NSS_CH0_RST_RX_CLK_N_RESET 128 ++#define NSS_CH0_RST_TX_CLK_N_RESET 129 ++#define NSS_CH0_RST_RX_125M_N_RESET 130 ++#define NSS_CH0_HW_RST_RX_125M_N_RESET 131 ++#define NSS_CH0_RST_TX_125M_N_RESET 132 ++#define NSS_CH1_RST_RX_CLK_N_RESET 133 ++#define NSS_CH1_RST_TX_CLK_N_RESET 134 ++#define NSS_CH1_RST_RX_125M_N_RESET 135 ++#define NSS_CH1_HW_RST_RX_125M_N_RESET 136 ++#define NSS_CH1_RST_TX_125M_N_RESET 137 ++#define NSS_CH2_RST_RX_CLK_N_RESET 138 ++#define NSS_CH2_RST_TX_CLK_N_RESET 139 ++#define NSS_CH2_RST_RX_125M_N_RESET 140 ++#define NSS_CH2_HW_RST_RX_125M_N_RESET 141 ++#define NSS_CH2_RST_TX_125M_N_RESET 142 ++#define NSS_CH3_RST_RX_CLK_N_RESET 143 ++#define NSS_CH3_RST_TX_CLK_N_RESET 144 ++#define NSS_CH3_RST_RX_125M_N_RESET 145 ++#define NSS_CH3_HW_RST_RX_125M_N_RESET 146 ++#define NSS_CH3_RST_TX_125M_N_RESET 147 ++#define NSS_RST_RX_250M_125M_N_RESET 148 ++#define NSS_RST_TX_250M_125M_N_RESET 149 ++#define NSS_QSGMII_TXPI_RST_N_RESET 150 ++#define NSS_QSGMII_CDR_RST_N_RESET 151 ++#define NSS_SGMII2_CDR_RST_N_RESET 152 ++#define NSS_SGMII3_CDR_RST_N_RESET 153 ++#define NSS_CAL_PRBS_RST_N_RESET 154 ++#define NSS_LCKDT_RST_N_RESET 155 ++#define NSS_SRDS_N_RESET 156 ++ + #endif +--- a/include/dt-bindings/clock/qcom,gcc-ipq806x.h ++++ b/include/dt-bindings/clock/qcom,gcc-ipq806x.h +@@ -289,5 +289,7 @@ + #define UBI32_CORE1_CLK 279 + #define UBI32_CORE2_CLK 280 + #define EBI2_AON_CLK 281 ++#define NSSTCM_CLK_SRC 282 ++#define NSSTCM_CLK 283 + + #endif diff --git a/target/linux/ipq806x/patches-4.1/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch b/target/linux/ipq806x/patches-4.1/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch new file mode 100644 index 0000000..3711e12 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/701-stmmac-add-phy-handle-support-to-the-platform-layer.patch @@ -0,0 +1,105 @@ +From 4f09499bc1d9bb095caccbcd73ff951ee631e521 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 15:42:40 -0700 +Subject: [PATCH 1/8] stmmac: add phy-handle support to the platform layer + +On stmmac driver, PHY specification in device-tree was done using the +non-standard property "snps,phy-addr". Specifying a PHY on a different +MDIO bus that the one within the stmmac controller doesn't seem to be +possible when device-tree is used. + +This change adds support for the phy-handle property, as specified in +Documentation/devicetree/bindings/net/ethernet.txt. + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 28 ++++++++++++++-------- + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 6 ++++- + include/linux/stmmac.h | 1 + + 3 files changed, 24 insertions(+), 11 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +@@ -52,6 +52,7 @@ + #include "stmmac_ptp.h" + #include "stmmac.h" + #include ++#include + + #define STMMAC_ALIGN(x) L1_CACHE_ALIGN(x) + +@@ -816,18 +817,25 @@ static int stmmac_init_phy(struct net_de + priv->speed = 0; + priv->oldduplex = -1; + +- if (priv->plat->phy_bus_name) +- snprintf(bus_id, MII_BUS_ID_SIZE, "%s-%x", +- priv->plat->phy_bus_name, priv->plat->bus_id); +- else +- snprintf(bus_id, MII_BUS_ID_SIZE, "stmmac-%x", +- priv->plat->bus_id); +- +- snprintf(phy_id_fmt, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, bus_id, +- priv->plat->phy_addr); +- pr_debug("stmmac_init_phy: trying to attach to %s\n", phy_id_fmt); ++ if (priv->plat->phy_node) { ++ phydev = of_phy_connect(dev, priv->plat->phy_node, ++ &stmmac_adjust_link, 0, interface); ++ } else { ++ if (priv->plat->phy_bus_name) ++ snprintf(bus_id, MII_BUS_ID_SIZE, "%s-%x", ++ priv->plat->phy_bus_name, priv->plat->bus_id); ++ else ++ snprintf(bus_id, MII_BUS_ID_SIZE, "stmmac-%x", ++ priv->plat->bus_id); ++ ++ snprintf(phy_id_fmt, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, bus_id, ++ priv->plat->phy_addr); ++ pr_debug("stmmac_init_phy: trying to attach to %s\n", ++ phy_id_fmt); + +- phydev = phy_connect(dev, phy_id_fmt, &stmmac_adjust_link, interface); ++ phydev = phy_connect(dev, phy_id_fmt, &stmmac_adjust_link, ++ interface); ++ } + + if (IS_ERR(phydev)) { + pr_err("%s: Could not attach to PHY\n", dev->name); +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + + #include "stmmac.h" + #include "stmmac_platform.h" +@@ -168,13 +169,16 @@ static int stmmac_probe_config_dt(struct + /* Default to phy auto-detection */ + plat->phy_addr = -1; + ++ /* If we find a phy-handle property, use it as the PHY */ ++ plat->phy_node = of_parse_phandle(np, "phy-handle", 0); ++ + /* "snps,phy-addr" is not a standard property. Mark it as deprecated + * and warn of its use. Remove this when phy node support is added. + */ + if (of_property_read_u32(np, "snps,phy-addr", &plat->phy_addr) == 0) + dev_warn(&pdev->dev, "snps,phy-addr property is deprecated\n"); + +- if (plat->phy_bus_name) ++ if (plat->phy_node || plat->phy_bus_name) + plat->mdio_bus_data = NULL; + else + plat->mdio_bus_data = +--- a/include/linux/stmmac.h ++++ b/include/linux/stmmac.h +@@ -99,6 +99,7 @@ struct plat_stmmacenet_data { + int phy_addr; + int interface; + struct stmmac_mdio_bus_data *mdio_bus_data; ++ struct device_node *phy_node; + struct stmmac_dma_cfg *dma_cfg; + int clk_csr; + int has_gmac; diff --git a/target/linux/ipq806x/patches-4.1/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch b/target/linux/ipq806x/patches-4.1/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch new file mode 100644 index 0000000..37f1002 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/702-stmmac-move-error-path-at-the-end-of-stmmac_probe_co.patch @@ -0,0 +1,65 @@ +From 0149d275415cd1b2382ce94e5eb32641590097d0 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 15:57:12 -0700 +Subject: [PATCH 2/8] stmmac: move error path at the end of + stmmac_probe_config_dt() + +We will want to do additional clean-up on certain errors. Therefore, +this change moves the error path at the end of the function for better +code readability. + +This patch doesn't change anything functionally. + +Signed-off-by: Mathieu Olivari +--- + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 22 ++++++++++++++++------ + 1 file changed, 16 insertions(+), 6 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -130,13 +130,18 @@ static int stmmac_probe_config_dt(struct + struct device_node *np = pdev->dev.of_node; + struct stmmac_dma_cfg *dma_cfg; + const struct of_device_id *device; ++ int ret; + +- if (!np) +- return -ENODEV; ++ if (!np) { ++ ret = -ENODEV; ++ goto err; ++ } + + device = of_match_device(stmmac_dt_ids, &pdev->dev); +- if (!device) +- return -ENODEV; ++ if (!device) { ++ ret = -ENODEV; ++ goto err; ++ } + + if (device->data) { + const struct stmmac_of_data *data = device->data; +@@ -236,8 +241,10 @@ static int stmmac_probe_config_dt(struct + if (of_find_property(np, "snps,pbl", NULL)) { + dma_cfg = devm_kzalloc(&pdev->dev, sizeof(*dma_cfg), + GFP_KERNEL); +- if (!dma_cfg) +- return -ENOMEM; ++ if (!dma_cfg) { ++ ret = -ENOMEM; ++ goto err; ++ } + plat->dma_cfg = dma_cfg; + of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); + dma_cfg->fixed_burst = +@@ -255,6 +262,9 @@ static int stmmac_probe_config_dt(struct + } + + return 0; ++ ++err: ++ return ret; + } + #else + static int stmmac_probe_config_dt(struct platform_device *pdev, diff --git a/target/linux/ipq806x/patches-4.1/703-stmmac-add-fixed-link-device-tree-support.patch b/target/linux/ipq806x/patches-4.1/703-stmmac-add-fixed-link-device-tree-support.patch new file mode 100644 index 0000000..a05875b --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/703-stmmac-add-fixed-link-device-tree-support.patch @@ -0,0 +1,64 @@ +From 3a95f75867be562cb919ff23a738f70357188fbd Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 16:02:03 -0700 +Subject: [PATCH 3/8] stmmac: add fixed-link device-tree support + +In case DT is used, this change adds the ability to the stmmac driver to +detect a fixed-link PHY, instanciate it, and use it during +phy_connect(). + +Fixed link PHYs DT usage is described in: +Documentation/devicetree/bindings/net/fixed-link.txt + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_main.c | 2 +- + drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 12 +++++++++++- + 2 files changed, 12 insertions(+), 2 deletions(-) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +@@ -856,7 +856,7 @@ static int stmmac_init_phy(struct net_de + * device as well. + * Note: phydev->phy_id is the result of reading the UID PHY registers. + */ +- if (phydev->phy_id == 0) { ++ if (!priv->plat->phy_node && phydev->phy_id == 0) { + phy_disconnect(phydev); + return -ENODEV; + } +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -177,6 +177,14 @@ static int stmmac_probe_config_dt(struct + /* If we find a phy-handle property, use it as the PHY */ + plat->phy_node = of_parse_phandle(np, "phy-handle", 0); + ++ /* If phy-handle is not specified, check if we have a fixed-phy */ ++ if (!plat->phy_node && of_phy_is_fixed_link(np)) { ++ if ((of_phy_register_fixed_link(np) < 0)) ++ return -ENODEV; ++ ++ plat->phy_node = of_node_get(np); ++ } ++ + /* "snps,phy-addr" is not a standard property. Mark it as deprecated + * and warn of its use. Remove this when phy node support is added. + */ +@@ -243,7 +251,7 @@ static int stmmac_probe_config_dt(struct + GFP_KERNEL); + if (!dma_cfg) { + ret = -ENOMEM; +- goto err; ++ goto err2; + } + plat->dma_cfg = dma_cfg; + of_property_read_u32(np, "snps,pbl", &dma_cfg->pbl); +@@ -263,6 +271,8 @@ static int stmmac_probe_config_dt(struct + + return 0; + ++err2: ++ of_node_put(np); + err: + return ret; + } diff --git a/target/linux/ipq806x/patches-4.1/704-stmmac-add-ipq806x-glue-layer.patch b/target/linux/ipq806x/patches-4.1/704-stmmac-add-ipq806x-glue-layer.patch new file mode 100644 index 0000000..7fd72be --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/704-stmmac-add-ipq806x-glue-layer.patch @@ -0,0 +1,407 @@ +From 69fb970ad3fe05af7cb99ea78230c69c7ca0d03b Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 16:10:22 -0700 +Subject: [PATCH 4/8] stmmac: add ipq806x glue layer + +The ethernet controller available in IPQ806x is a Synopsys DesignWare +Gigabit MAC IP core, already supported by the stmmac driver. + +This glue layer implements some platform specific settings required to +get the controller working on an IPQ806x based platform. + +Signed-off-by: Mathieu Olivari +--- + drivers/net/ethernet/stmicro/stmmac/Kconfig | 1 + + drivers/net/ethernet/stmicro/stmmac/Makefile | 2 +- + drivers/net/ethernet/stmicro/stmmac/dwmac-ipq.c | 324 +++++++++++++++++++++ + .../net/ethernet/stmicro/stmmac/stmmac_platform.c | 1 + + .../net/ethernet/stmicro/stmmac/stmmac_platform.h | 1 + + 5 files changed, 328 insertions(+), 1 deletion(-) + create mode 100644 drivers/net/ethernet/stmicro/stmmac/dwmac-ipq.c + +--- a/drivers/net/ethernet/stmicro/stmmac/Kconfig ++++ b/drivers/net/ethernet/stmicro/stmmac/Kconfig +@@ -16,6 +16,7 @@ if STMMAC_ETH + config STMMAC_PLATFORM + tristate "STMMAC Platform bus support" + depends on STMMAC_ETH ++ select MFD_SYSCON + default y + ---help--- + This selects the platform specific bus support for the stmmac driver. +--- a/drivers/net/ethernet/stmicro/stmmac/Makefile ++++ b/drivers/net/ethernet/stmicro/stmmac/Makefile +@@ -6,7 +6,7 @@ stmmac-objs:= stmmac_main.o stmmac_ethto + + obj-$(CONFIG_STMMAC_PLATFORM) += stmmac-platform.o + stmmac-platform-objs:= stmmac_platform.o dwmac-meson.o dwmac-sunxi.o \ +- dwmac-sti.o dwmac-socfpga.o dwmac-rk.o ++ dwmac-sti.o dwmac-socfpga.o dwmac-rk.o dwmac-ipq806x.o + + obj-$(CONFIG_STMMAC_PCI) += stmmac-pci.o + stmmac-pci-objs:= stmmac_pci.o +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -43,6 +43,7 @@ static const struct of_device_id stmmac_ + { .compatible = "st,stid127-dwmac", .data = &stid127_dwmac_data}, + { .compatible = "st,stih407-dwmac", .data = &stih4xx_dwmac_data}, + { .compatible = "altr,socfpga-stmmac", .data = &socfpga_gmac_data }, ++ { .compatible = "qcom,ipq806x-gmac", .data = &ipq806x_gmac_data }, + { .compatible = "st,spear600-gmac"}, + { .compatible = "snps,dwmac-3.610"}, + { .compatible = "snps,dwmac-3.70a"}, +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.h +@@ -25,5 +25,6 @@ extern const struct stmmac_of_data stih4 + extern const struct stmmac_of_data stid127_dwmac_data; + extern const struct stmmac_of_data socfpga_gmac_data; + extern const struct stmmac_of_data rk3288_gmac_data; ++extern const struct stmmac_of_data ipq806x_gmac_data; + + #endif /* __STMMAC_PLATFORM_H__ */ +--- /dev/null ++++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-ipq806x.c +@@ -0,0 +1,343 @@ ++/* ++ * Qualcomm Atheros IPQ806x GMAC glue layer ++ * ++ * Copyright (C) 2015 The Linux Foundation ++ * ++ * Permission to use, copy, modify, and/or distribute this software for any ++ * purpose with or without fee is hereby granted, provided that the above ++ * copyright notice and this permission notice appear in all copies. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES ++ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ++ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES ++ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ++ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF ++ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "stmmac_platform.h" ++ ++#define NSS_COMMON_CLK_GATE 0x8 ++#define NSS_COMMON_CLK_GATE_PTP_EN(x) BIT(0x10 + x) ++#define NSS_COMMON_CLK_GATE_RGMII_RX_EN(x) BIT(0x9 + (x * 2)) ++#define NSS_COMMON_CLK_GATE_RGMII_TX_EN(x) BIT(0x8 + (x * 2)) ++#define NSS_COMMON_CLK_GATE_GMII_RX_EN(x) BIT(0x4 + x) ++#define NSS_COMMON_CLK_GATE_GMII_TX_EN(x) BIT(0x0 + x) ++ ++#define NSS_COMMON_CLK_DIV0 0xC ++#define NSS_COMMON_CLK_DIV_OFFSET(x) (x * 8) ++#define NSS_COMMON_CLK_DIV_MASK 0x7f ++ ++#define NSS_COMMON_CLK_SRC_CTRL 0x14 ++#define NSS_COMMON_CLK_SRC_CTRL_OFFSET(x) (1 << x) ++/* Mode is coded on 1 bit but is different depending on the MAC ID: ++ * MAC0: QSGMII=0 RGMII=1 ++ * MAC1: QSGMII=0 SGMII=0 RGMII=1 ++ * MAC2 & MAC3: QSGMII=0 SGMII=1 ++ */ ++#define NSS_COMMON_CLK_SRC_CTRL_RGMII(x) 1 ++#define NSS_COMMON_CLK_SRC_CTRL_SGMII(x) ((x >= 2) ? 1 : 0) ++ ++#define NSS_COMMON_MACSEC_CTL 0x28 ++#define NSS_COMMON_MACSEC_CTL_EXT_BYPASS_EN(x) (1 << x) ++ ++#define NSS_COMMON_GMAC_CTL(x) (0x30 + (x * 4)) ++#define NSS_COMMON_GMAC_CTL_CSYS_REQ BIT(19) ++#define NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL BIT(16) ++#define NSS_COMMON_GMAC_CTL_IFG_LIMIT_OFFSET 8 ++#define NSS_COMMON_GMAC_CTL_IFG_OFFSET 0 ++#define NSS_COMMON_GMAC_CTL_IFG_MASK 0x3f ++ ++#define NSS_COMMON_CLK_DIV_RGMII_1000 1 ++#define NSS_COMMON_CLK_DIV_RGMII_100 9 ++#define NSS_COMMON_CLK_DIV_RGMII_10 99 ++#define NSS_COMMON_CLK_DIV_SGMII_1000 0 ++#define NSS_COMMON_CLK_DIV_SGMII_100 4 ++#define NSS_COMMON_CLK_DIV_SGMII_10 49 ++ ++#define QSGMII_PCS_MODE_CTL 0x68 ++#define QSGMII_PCS_MODE_CTL_AUTONEG_EN(x) BIT((x * 8) + 7) ++ ++#define QSGMII_PCS_CAL_LCKDT_CTL 0x120 ++#define QSGMII_PCS_CAL_LCKDT_CTL_RST BIT(19) ++ ++/* Only GMAC1/2/3 support SGMII and their CTL register are not contiguous */ ++#define QSGMII_PHY_SGMII_CTL(x) ((x == 1) ? 0x134 : \ ++ (0x13c + (4 * (x - 2)))) ++#define QSGMII_PHY_CDR_EN BIT(0) ++#define QSGMII_PHY_RX_FRONT_EN BIT(1) ++#define QSGMII_PHY_RX_SIGNAL_DETECT_EN BIT(2) ++#define QSGMII_PHY_TX_DRIVER_EN BIT(3) ++#define QSGMII_PHY_QSGMII_EN BIT(7) ++#define QSGMII_PHY_PHASE_LOOP_GAIN_OFFSET 12 ++#define QSGMII_PHY_PHASE_LOOP_GAIN_MASK 0x7 ++#define QSGMII_PHY_RX_DC_BIAS_OFFSET 18 ++#define QSGMII_PHY_RX_DC_BIAS_MASK 0x3 ++#define QSGMII_PHY_RX_INPUT_EQU_OFFSET 20 ++#define QSGMII_PHY_RX_INPUT_EQU_MASK 0x3 ++#define QSGMII_PHY_CDR_PI_SLEW_OFFSET 22 ++#define QSGMII_PHY_CDR_PI_SLEW_MASK 0x3 ++#define QSGMII_PHY_TX_DRV_AMP_OFFSET 28 ++#define QSGMII_PHY_TX_DRV_AMP_MASK 0xf ++ ++struct ipq806x_gmac { ++ struct platform_device *pdev; ++ struct regmap *nss_common; ++ struct regmap *qsgmii_csr; ++ uint32_t id; ++ struct clk *core_clk; ++ phy_interface_t phy_mode; ++}; ++ ++static int get_clk_div_sgmii(struct ipq806x_gmac *gmac, unsigned int speed) ++{ ++ struct device *dev = &gmac->pdev->dev; ++ int div; ++ ++ switch (speed) { ++ case SPEED_1000: ++ div = NSS_COMMON_CLK_DIV_SGMII_1000; ++ break; ++ ++ case SPEED_100: ++ div = NSS_COMMON_CLK_DIV_SGMII_100; ++ break; ++ ++ case SPEED_10: ++ div = NSS_COMMON_CLK_DIV_SGMII_10; ++ break; ++ ++ default: ++ dev_err(dev, "Speed %dMbps not supported in SGMII\n", speed); ++ return -EINVAL; ++ } ++ ++ return div; ++} ++ ++static int get_clk_div_rgmii(struct ipq806x_gmac *gmac, unsigned int speed) ++{ ++ struct device *dev = &gmac->pdev->dev; ++ int div; ++ ++ switch (speed) { ++ case SPEED_1000: ++ div = NSS_COMMON_CLK_DIV_RGMII_1000; ++ break; ++ ++ case SPEED_100: ++ div = NSS_COMMON_CLK_DIV_RGMII_100; ++ break; ++ ++ case SPEED_10: ++ div = NSS_COMMON_CLK_DIV_RGMII_10; ++ break; ++ ++ default: ++ dev_err(dev, "Speed %dMbps not supported in RGMII\n", speed); ++ return -EINVAL; ++ } ++ ++ return div; ++} ++ ++static int ipq806x_gmac_set_speed(struct ipq806x_gmac *gmac, unsigned int speed) ++{ ++ uint32_t clk_bits, val; ++ int div; ++ ++ switch (gmac->phy_mode) { ++ case PHY_INTERFACE_MODE_RGMII: ++ div = get_clk_div_rgmii(gmac, speed); ++ clk_bits = NSS_COMMON_CLK_GATE_RGMII_RX_EN(gmac->id) | ++ NSS_COMMON_CLK_GATE_RGMII_TX_EN(gmac->id); ++ break; ++ ++ case PHY_INTERFACE_MODE_SGMII: ++ div = get_clk_div_sgmii(gmac, speed); ++ clk_bits = NSS_COMMON_CLK_GATE_GMII_RX_EN(gmac->id) | ++ NSS_COMMON_CLK_GATE_GMII_TX_EN(gmac->id); ++ break; ++ ++ default: ++ dev_err(&gmac->pdev->dev, "Unsupported PHY mode: \"%s\"\n", ++ phy_modes(gmac->phy_mode)); ++ return -EINVAL; ++ } ++ ++ /* Disable the clocks */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val); ++ val &= ~clk_bits; ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val); ++ ++ /* Set the divider */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_DIV0, &val); ++ val &= ~(NSS_COMMON_CLK_DIV_MASK ++ << NSS_COMMON_CLK_DIV_OFFSET(gmac->id)); ++ val |= div << NSS_COMMON_CLK_DIV_OFFSET(gmac->id); ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_DIV0, val); ++ ++ /* Enable the clock back */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val); ++ val |= clk_bits; ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val); ++ ++ return 0; ++} ++ ++static void *ipq806x_gmac_of_parse(struct ipq806x_gmac *gmac) ++{ ++ struct device *dev = &gmac->pdev->dev; ++ ++ gmac->phy_mode = of_get_phy_mode(dev->of_node); ++ if (gmac->phy_mode < 0) { ++ dev_err(dev, "missing phy mode property\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ if (of_property_read_u32(dev->of_node, "qcom,id", &gmac->id) < 0) { ++ dev_err(dev, "missing qcom id property\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ /* The GMACs are called 1 to 4 in the documentation, but to simplify the ++ * code and keep it consistent with the Linux convention, we'll number ++ * them from 0 to 3 here. ++ */ ++ if (gmac->id < 0 || gmac->id > 3) { ++ dev_err(dev, "invalid gmac id\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ gmac->core_clk = devm_clk_get(dev, "stmmaceth"); ++ if (IS_ERR(gmac->core_clk)) { ++ dev_err(dev, "missing stmmaceth clk property\n"); ++ return gmac->core_clk; ++ } ++ clk_set_rate(gmac->core_clk, 266000000); ++ ++ /* Setup the register map for the nss common registers */ ++ gmac->nss_common = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "qcom,nss-common"); ++ if (IS_ERR(gmac->nss_common)) { ++ dev_err(dev, "missing nss-common node\n"); ++ return gmac->nss_common; ++ } ++ ++ /* Setup the register map for the qsgmii csr registers */ ++ gmac->qsgmii_csr = syscon_regmap_lookup_by_phandle(dev->of_node, ++ "qcom,qsgmii-csr"); ++ if (IS_ERR(gmac->qsgmii_csr)) { ++ dev_err(dev, "missing qsgmii-csr node\n"); ++ return gmac->qsgmii_csr; ++ } ++ ++ return NULL; ++} ++ ++static void *ipq806x_gmac_setup(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct ipq806x_gmac *gmac; ++ int val; ++ void *err; ++ ++ gmac = devm_kzalloc(dev, sizeof(*gmac), GFP_KERNEL); ++ if (!gmac) ++ return ERR_PTR(-ENOMEM); ++ ++ gmac->pdev = pdev; ++ ++ err = ipq806x_gmac_of_parse(gmac); ++ if (err) { ++ dev_err(dev, "device tree parsing error\n"); ++ return err; ++ } ++ ++ regmap_write(gmac->qsgmii_csr, QSGMII_PCS_CAL_LCKDT_CTL, ++ QSGMII_PCS_CAL_LCKDT_CTL_RST); ++ ++ /* Inter frame gap is set to 12 */ ++ val = 12 << NSS_COMMON_GMAC_CTL_IFG_OFFSET | ++ 12 << NSS_COMMON_GMAC_CTL_IFG_LIMIT_OFFSET; ++ /* We also initiate an AXI low power exit request */ ++ val |= NSS_COMMON_GMAC_CTL_CSYS_REQ; ++ switch (gmac->phy_mode) { ++ case PHY_INTERFACE_MODE_RGMII: ++ val |= NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL; ++ break; ++ case PHY_INTERFACE_MODE_SGMII: ++ val &= ~NSS_COMMON_GMAC_CTL_PHY_IFACE_SEL; ++ break; ++ default: ++ dev_err(&pdev->dev, "Unsupported PHY mode: \"%s\"\n", ++ phy_modes(gmac->phy_mode)); ++ return NULL; ++ } ++ regmap_write(gmac->nss_common, NSS_COMMON_GMAC_CTL(gmac->id), val); ++ ++ /* Configure the clock src according to the mode */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_SRC_CTRL, &val); ++ val &= ~NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id); ++ switch (gmac->phy_mode) { ++ case PHY_INTERFACE_MODE_RGMII: ++ val |= NSS_COMMON_CLK_SRC_CTRL_RGMII(gmac->id) << ++ NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id); ++ break; ++ case PHY_INTERFACE_MODE_SGMII: ++ val |= NSS_COMMON_CLK_SRC_CTRL_SGMII(gmac->id) << ++ NSS_COMMON_CLK_SRC_CTRL_OFFSET(gmac->id); ++ break; ++ default: ++ dev_err(&pdev->dev, "Unsupported PHY mode: \"%s\"\n", ++ phy_modes(gmac->phy_mode)); ++ return NULL; ++ } ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_SRC_CTRL, val); ++ ++ /* Enable PTP clock */ ++ regmap_read(gmac->nss_common, NSS_COMMON_CLK_GATE, &val); ++ val |= NSS_COMMON_CLK_GATE_PTP_EN(gmac->id); ++ regmap_write(gmac->nss_common, NSS_COMMON_CLK_GATE, val); ++ ++ if (gmac->phy_mode == PHY_INTERFACE_MODE_SGMII) { ++ regmap_write(gmac->qsgmii_csr, QSGMII_PHY_SGMII_CTL(gmac->id), ++ QSGMII_PHY_CDR_EN | ++ QSGMII_PHY_RX_FRONT_EN | ++ QSGMII_PHY_RX_SIGNAL_DETECT_EN | ++ QSGMII_PHY_TX_DRIVER_EN | ++ QSGMII_PHY_QSGMII_EN | ++ 0x4 << QSGMII_PHY_PHASE_LOOP_GAIN_OFFSET | ++ 0x3 << QSGMII_PHY_RX_DC_BIAS_OFFSET | ++ 0x1 << QSGMII_PHY_RX_INPUT_EQU_OFFSET | ++ 0x2 << QSGMII_PHY_CDR_PI_SLEW_OFFSET | ++ 0xC << QSGMII_PHY_TX_DRV_AMP_OFFSET); ++ } ++ ++ return gmac; ++} ++ ++static void ipq806x_gmac_fix_mac_speed(void *priv, unsigned int speed) ++{ ++ struct ipq806x_gmac *gmac = priv; ++ ++ ipq806x_gmac_set_speed(gmac, speed); ++} ++ ++const struct stmmac_of_data ipq806x_gmac_data = { ++ .has_gmac = 1, ++ .setup = ipq806x_gmac_setup, ++ .fix_mac_speed = ipq806x_gmac_fix_mac_speed, ++}; diff --git a/target/linux/ipq806x/patches-4.1/705-net-stmmac-ipq806x-document-device-tree-bindings.patch b/target/linux/ipq806x/patches-4.1/705-net-stmmac-ipq806x-document-device-tree-bindings.patch new file mode 100644 index 0000000..3144fa3 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/705-net-stmmac-ipq806x-document-device-tree-bindings.patch @@ -0,0 +1,52 @@ +From 0f9605d9409b77a89daef91cc68239fc2ff50457 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Fri, 8 May 2015 16:51:25 -0700 +Subject: [PATCH 5/8] net: stmmac: ipq806x: document device tree bindings + +Add the device tree bindings documentation for the QCA IPQ806x +variant of the Synopsys DesignWare MAC. + +Signed-off-by: Mathieu Olivari +--- + .../devicetree/bindings/net/ipq806x-dwmac.txt | 35 ++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + create mode 100644 Documentation/devicetree/bindings/net/ipq806x-dwmac.txt + +--- /dev/null ++++ b/Documentation/devicetree/bindings/net/ipq806x-dwmac.txt +@@ -0,0 +1,35 @@ ++* IPQ806x DWMAC Ethernet controller ++ ++The device inherits all the properties of the dwmac/stmmac devices ++described in the file net/stmmac.txt with the following changes. ++ ++Required properties: ++ ++- compatible: should be "qcom,ipq806x-gmac" along with "snps,dwmac" ++ and any applicable more detailed version number ++ described in net/stmmac.txt ++ ++- qcom,nss-common: should contain a phandle to a syscon device mapping the ++ nss-common registers. ++ ++- qcom,qsgmii-csr: should contain a phandle to a syscon device mapping the ++ qsgmii-csr registers. ++ ++Example: ++ ++ gmac: ethernet@37000000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37000000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE1_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE1_RESET>; ++ reset-names = "stmmaceth"; ++ }; diff --git a/target/linux/ipq806x/patches-4.1/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch b/target/linux/ipq806x/patches-4.1/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch new file mode 100644 index 0000000..0e2b305 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/707-ARM-dts-qcom-add-mdio-nodes-to-ap148-db149.patch @@ -0,0 +1,146 @@ +From e81de9d28bd0421c236df322872e64edf4ee1852 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Mon, 11 May 2015 16:32:09 -0700 +Subject: [PATCH 7/8] ARM: dts: qcom: add mdio nodes to ap148 & db149 + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 40 ++++++++++++++++++++++++++- + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 46 ++++++++++++++++++++++++++++++++ + 2 files changed, 85 insertions(+), 1 deletion(-) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -19,8 +19,9 @@ + }; + }; + +- alias { ++ aliases { + serial0 = &uart4; ++ mdio-gpio0 = &mdio0; + }; + + chosen { +@@ -83,6 +84,15 @@ + bias-bus-hold; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -157,6 +167,34 @@ + + linux,part-probe = "qcom-smem"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -16,6 +16,7 @@ + + alias { + serial0 = &uart2; ++ mdio-gpio0 = &mdio0; + }; + + chosen { +@@ -65,6 +66,15 @@ + bias-none; + }; + }; ++ ++ mdio0_pins: mdio0_pins { ++ mux { ++ pins = "gpio0", "gpio1"; ++ function = "gpio"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi2: gsbi@12480000 { +@@ -176,5 +186,44 @@ + pinctrl-0 = <&pcie2_pins>; + pinctrl-names = "default"; + }; ++ ++ mdio0: mdio { ++ compatible = "virtual,mdio-gpio"; ++ #address-cells = <1>; ++ #size-cells = <0>; ++ gpios = <&qcom_pinmux 1 0 &qcom_pinmux 0 0>; ++ ++ pinctrl-0 = <&mdio0_pins>; ++ pinctrl-names = "default"; ++ ++ phy0: ethernet-phy@0 { ++ device_type = "ethernet-phy"; ++ reg = <0>; ++ qca,ar8327-initvals = < ++ 0x00004 0x7600000 /* PAD0_MODE */ ++ 0x00008 0x1000000 /* PAD5_MODE */ ++ 0x0000c 0x80 /* PAD6_MODE */ ++ 0x000e4 0xaa545 /* MAC_POWER_SEL */ ++ 0x000e0 0xc74164de /* SGMII_CTRL */ ++ 0x0007c 0x4e /* PORT0_STATUS */ ++ 0x00094 0x4e /* PORT6_STATUS */ ++ >; ++ }; ++ ++ phy4: ethernet-phy@4 { ++ device_type = "ethernet-phy"; ++ reg = <4>; ++ }; ++ ++ phy6: ethernet-phy@6 { ++ device_type = "ethernet-phy"; ++ reg = <6>; ++ }; ++ ++ phy7: ethernet-phy@7 { ++ device_type = "ethernet-phy"; ++ reg = <7>; ++ }; ++ }; + }; + }; diff --git a/target/linux/ipq806x/patches-4.1/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch b/target/linux/ipq806x/patches-4.1/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch new file mode 100644 index 0000000..0918634 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/708-ARM-dts-qcom-add-gmac-nodes-to-ipq806x-platforms.patch @@ -0,0 +1,212 @@ +From cab1f4720e82f2e17eaeed9a9ad9e4f07c742977 Mon Sep 17 00:00:00 2001 +From: Mathieu Olivari +Date: Mon, 11 May 2015 12:29:18 -0700 +Subject: [PATCH 8/8] ARM: dts: qcom: add gmac nodes to ipq806x platforms + +Signed-off-by: Mathieu Olivari +--- + arch/arm/boot/dts/qcom-ipq8064-ap148.dts | 31 ++++++++++++ + arch/arm/boot/dts/qcom-ipq8064-db149.dts | 43 ++++++++++++++++ + arch/arm/boot/dts/qcom-ipq8064.dtsi | 86 ++++++++++++++++++++++++++++++++ + 3 files changed, 160 insertions(+) + +--- a/arch/arm/boot/dts/qcom-ipq8064-ap148.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-ap148.dts +@@ -93,6 +93,16 @@ + bias-disable; + }; + }; ++ ++ rgmii2_pins: rgmii2_pins { ++ mux { ++ pins = "gpio27", "gpio28", "gpio29", "gpio30", "gpio31", "gpio32", ++ "gpio51", "gpio52", "gpio59", "gpio60", "gpio61", "gpio62" ; ++ function = "rgmii2"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi@16300000 { +@@ -195,6 +205,27 @@ + reg = <4>; + }; + }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ phy-handle = <&phy4>; ++ qcom,id = <1>; ++ ++ pinctrl-0 = <&rgmii2_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; + }; + }; + +--- a/arch/arm/boot/dts/qcom-ipq8064-db149.dts ++++ b/arch/arm/boot/dts/qcom-ipq8064-db149.dts +@@ -75,6 +75,14 @@ + bias-disable; + }; + }; ++ ++ rgmii0_pins: rgmii0_pins { ++ mux { ++ pins = "gpio2", "gpio66"; ++ drive-strength = <8>; ++ bias-disable; ++ }; ++ }; + }; + + gsbi2: gsbi@12480000 { +@@ -225,5 +233,40 @@ + reg = <7>; + }; + }; ++ ++ gmac0: ethernet@37000000 { ++ status = "ok"; ++ phy-mode = "rgmii"; ++ qcom,id = <0>; ++ phy-handle = <&phy4>; ++ ++ pinctrl-0 = <&rgmii0_pins>; ++ pinctrl-names = "default"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <1>; ++ ++ fixed-link { ++ speed = <1000>; ++ full-duplex; ++ }; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <2>; ++ phy-handle = <&phy6>; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ status = "ok"; ++ phy-mode = "sgmii"; ++ qcom,id = <3>; ++ phy-handle = <&phy7>; ++ }; + }; + }; +--- a/arch/arm/boot/dts/qcom-ipq8064.dtsi ++++ b/arch/arm/boot/dts/qcom-ipq8064.dtsi +@@ -636,6 +636,92 @@ + + status = "disabled"; + }; ++ ++ nss_common: syscon@03000000 { ++ compatible = "syscon"; ++ reg = <0x03000000 0x0000FFFF>; ++ }; ++ ++ qsgmii_csr: syscon@1bb00000 { ++ compatible = "syscon"; ++ reg = <0x1bb00000 0x000001FF>; ++ }; ++ ++ gmac0: ethernet@37000000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37000000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE1_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE1_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac1: ethernet@37200000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37200000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE2_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE2_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac2: ethernet@37400000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37400000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE3_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE3_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; ++ ++ gmac3: ethernet@37600000 { ++ device_type = "network"; ++ compatible = "qcom,ipq806x-gmac"; ++ reg = <0x37600000 0x200000>; ++ interrupts = ; ++ interrupt-names = "macirq"; ++ ++ qcom,nss-common = <&nss_common>; ++ qcom,qsgmii-csr = <&qsgmii_csr>; ++ ++ clocks = <&gcc GMAC_CORE4_CLK>; ++ clock-names = "stmmaceth"; ++ ++ resets = <&gcc GMAC_CORE4_RESET>; ++ reset-names = "stmmaceth"; ++ ++ status = "disabled"; ++ }; + }; + + sfpb_mutex: sfpb-mutex { diff --git a/target/linux/ipq806x/patches-4.1/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch b/target/linux/ipq806x/patches-4.1/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch new file mode 100644 index 0000000..84c15c7 --- /dev/null +++ b/target/linux/ipq806x/patches-4.1/709-stmac-platform-add-support-for-retreiving-mac-from-m.patch @@ -0,0 +1,35 @@ +From 5bf2dabde1fa3af0c9082b42b6847ef3fd198b13 Mon Sep 17 00:00:00 2001 +From: Jonas Gorski +Date: Sun, 9 Aug 2015 12:53:55 +0200 +Subject: [PATCH] stmac: platform: add support for retreiving mac from mtd + +--- + drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c ++++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c +@@ -302,6 +302,7 @@ static int stmmac_pltfr_probe(struct pla + struct stmmac_priv *priv = NULL; + struct plat_stmmacenet_data *plat_dat = NULL; + const char *mac = NULL; ++ u8 mtd_mac[ETH_ALEN] = { }; + int irq, wol_irq, lpi_irq; + + /* Get IRQ information early to have an ability to ask for deferred +@@ -362,6 +363,15 @@ static int stmmac_pltfr_probe(struct pla + pr_err("%s: main dt probe failed", __func__); + return ret; + } ++ ++ if (!mac) { ++ ret = of_get_mac_address_mtd(dev->of_node, &mtd_mac); ++ if (ret == -EPROBE_DEFER) ++ return ret; ++ ++ if (is_valid_ether_addr(&mtd_mac)) ++ mac = mtd_mac; ++ } + } + + /* Custom setup (if needed) */ diff --git a/target/linux/ipq806x/profiles/default.mk b/target/linux/ipq806x/profiles/default.mk new file mode 100644 index 0000000..6469bcb --- /dev/null +++ b/target/linux/ipq806x/profiles/default.mk @@ -0,0 +1,19 @@ +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# Copyright (C) 2009 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +define Profile/Default + NAME:=Default Profile (minimum package set) + PACKAGES:= \ + kmod-usb-core kmod-usb-ohci kmod-usb2 kmod-ledtrig-usbdev \ + kmod-usb3 kmod-usb-dwc3-qcom kmod-usb-phy-qcom-dwc3 +endef + +define Profile/Default/Description + Default package set compatible with most boards. +endef +$(eval $(call Profile,Default)) diff --git a/target/linux/ipq806x/profiles/netgear.mk b/target/linux/ipq806x/profiles/netgear.mk new file mode 100644 index 0000000..3402f45 --- /dev/null +++ b/target/linux/ipq806x/profiles/netgear.mk @@ -0,0 +1,20 @@ +# +# Copyright (c) 2014 The Linux Foundation. All rights reserved. +# Copyright (C) 2009 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +define Profile/R7500 + NAME:=Netgear Nighthawk X4 R7500 + PACKAGES:= \ + kmod-usb-core kmod-usb-ohci kmod-usb2 kmod-ledtrig-usbdev \ + kmod-usb3 kmod-usb-dwc3-qcom kmod-usb-phy-qcom-dwc3 \ + kmod-ath10k wpad-mini +endef + +define Profile/R7500/Description + Package set for the Netgear Nighthawk X4 R7500. +endef +$(eval $(call Profile,R7500)) -- cgit v1.2.3