From 5f89c808839d7c5761a34adf15af5f9f7f9de053 Mon Sep 17 00:00:00 2001 From: Tianling Shen Date: Fri, 3 Jul 2026 16:39:38 +0800 Subject: [PATCH] sunxi: add PCIe support for Allwinner A523 Patches are taken from Armbian. Signed-off-by: Tianling Shen --- ...-fmsh-add-support-for-FM25G-01-02-B.patch} | 28 +- .../400-mtd-spinand-Support-fmsh.patch | 4 +- target/linux/sunxi/cortexa53/config-6.18 | 25 +- target/linux/sunxi/cortexa53/target.mk | 2 +- ...lk-a523-add-initial-cpufreq-support.patch} | 0 ...-02-clk-gate-add-regmap-gate-support.patch | 244 ++ ...x-clock-handling-for-ccu-sun55i-a523.patch | 57 + .../124-iommu-sunxi-add-iommu-driver.patch | 2363 ++++++++++++ ...5-phy-allwinner-add-pcie-usb3-driver.patch | 1229 +++++++ .../126-pci-sunxi-enable-pcie-support.patch | 3236 +++++++++++++++++ ...i-add-iommu-usbc-pcie-combophy-nodes.patch | 117 + ...lwinner-a527-Radxa-A5E-improvements.patch} | 0 ...-cubie-a5e-enable-usbc-pcie-combophy.patch | 82 + ...lwinner-add-LED-aliases-to-Cubie-A5E.patch | 4 +- ...702-net-phy-add-driver-for-MAXIO-phy.patch | 2 +- 15 files changed, 7366 insertions(+), 27 deletions(-) rename target/linux/generic/{pending-6.18/401-mtd-spinand-fmsh-add-support-for-FM25G0102B.patch => backport-6.18/436-v7.3-mtd-spinand-fmsh-add-support-for-FM25G-01-02-B.patch} (81%) rename target/linux/sunxi/patches-6.18/{121-clk-a523-add-initial-cpufreq-support.patch => 121-01-clk-a523-add-initial-cpufreq-support.patch} (100%) create mode 100644 target/linux/sunxi/patches-6.18/121-02-clk-gate-add-regmap-gate-support.patch create mode 100644 target/linux/sunxi/patches-6.18/121-03-clk-sunxi-ng-fix-clock-handling-for-ccu-sun55i-a523.patch create mode 100644 target/linux/sunxi/patches-6.18/124-iommu-sunxi-add-iommu-driver.patch create mode 100644 target/linux/sunxi/patches-6.18/125-phy-allwinner-add-pcie-usb3-driver.patch create mode 100644 target/linux/sunxi/patches-6.18/126-pci-sunxi-enable-pcie-support.patch create mode 100644 target/linux/sunxi/patches-6.18/130-04-arm64-dts-sun55i-dtsi-add-iommu-usbc-pcie-combophy-nodes.patch rename target/linux/sunxi/patches-6.18/{131-arm64-dts-allwinner-a527-Radxa-A5E-improvements.patch => 131-01-arm64-dts-allwinner-a527-Radxa-A5E-improvements.patch} (100%) create mode 100644 target/linux/sunxi/patches-6.18/131-02-arm64-dts-sun55i-a527-cubie-a5e-enable-usbc-pcie-combophy.patch diff --git a/target/linux/generic/pending-6.18/401-mtd-spinand-fmsh-add-support-for-FM25G0102B.patch b/target/linux/generic/backport-6.18/436-v7.3-mtd-spinand-fmsh-add-support-for-FM25G-01-02-B.patch similarity index 81% rename from target/linux/generic/pending-6.18/401-mtd-spinand-fmsh-add-support-for-FM25G0102B.patch rename to target/linux/generic/backport-6.18/436-v7.3-mtd-spinand-fmsh-add-support-for-FM25G-01-02-B.patch index e3a5a10e5b..19994654e1 100644 --- a/target/linux/generic/pending-6.18/401-mtd-spinand-fmsh-add-support-for-FM25G0102B.patch +++ b/target/linux/generic/backport-6.18/436-v7.3-mtd-spinand-fmsh-add-support-for-FM25G-01-02-B.patch @@ -1,7 +1,7 @@ +From d5a5c9eb2ee9ff5b4cc0be15ac7f4879d4c2f247 Mon Sep 17 00:00:00 2001 From: Ziyang Huang -To: miquel.raynal@bootlin.com -Subject: [PATCH v3] mtd: spinand: fmsh: add support for FM25G{01,02}B -Date: Sun, 31 May 2026 21:05:17 +0800 +Date: Tue, 23 Jun 2026 22:54:22 +0800 +Subject: [PATCH] mtd: spinand: fmsh: add support for FM25G{01,02}B Add support for FudanMicro FM25G01B SPI NAND and FudanMicro FM25G02B SPI NAND. @@ -10,19 +10,10 @@ FM25G01B datasheet: https://www.fmsh.com/nvm/FM25G01B_ds_eng.pdf FM25G02B datasheet: https://www.fmsh.com/nvm/FM25G02B_ds_eng.pdf Signed-off-by: Ziyang Huang -Link: https://lore.kernel.org/all/SEYPR01MB58821E380C5DD8F7B3FFDA23C9142@SEYPR01MB5882.apcprd01.prod.exchangelabs.com/ -[Fix chip names in the commit message, mask macro in the switch operator] -Signed-off-by: Mikhail Zhilkin +Signed-off-by: Miquel Raynal --- -Changes since v2: - More verbose commit message. - Use only one section in fm25g01b_ooblayout_free(). - -Changes since v1: - Fix copy-paste issue. (Correct FM25G01B size.) - - drivers/mtd/nand/spi/fmsh.c | 96 +++++++++++++++++++++++++++++++++++++ - 1 file changed, 96 insertions(+) + drivers/mtd/nand/spi/fmsh.c | 95 +++++++++++++++++++++++++++++++++++++ + 1 file changed, 95 insertions(+) --- a/drivers/mtd/nand/spi/fmsh.c +++ b/drivers/mtd/nand/spi/fmsh.c @@ -43,11 +34,10 @@ Changes since v1: #define FM25S01BI3_STATUS_ECC_MASK (7 << 4) #define FM25S01BI3_STATUS_ECC_NO_BITFLIPS (0 << 4) #define FM25S01BI3_STATUS_ECC_1_3_BITFLIPS (1 << 4) -@@ -34,6 +44,67 @@ static SPINAND_OP_VARIANTS(update_cache_ +@@ -34,6 +44,66 @@ static SPINAND_OP_VARIANTS(update_cache_ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0), SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0)); -+ +static int fm25g01b_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ @@ -111,7 +101,7 @@ Changes since v1: static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section, struct mtd_oob_region *region) { -@@ -102,6 +173,11 @@ static int fm25s01bi3_ooblayout_free(str +@@ -102,6 +172,11 @@ static int fm25s01bi3_ooblayout_free(str return 0; } @@ -123,7 +113,7 @@ Changes since v1: static const struct mtd_ooblayout_ops fm25s01a_ooblayout = { .ecc = fm25s01a_ooblayout_ecc, .free = fm25s01a_ooblayout_free, -@@ -113,6 +189,26 @@ static const struct mtd_ooblayout_ops fm +@@ -113,6 +188,26 @@ static const struct mtd_ooblayout_ops fm }; static const struct spinand_info fmsh_spinand_table[] = { diff --git a/target/linux/generic/hack-6.18/400-mtd-spinand-Support-fmsh.patch b/target/linux/generic/hack-6.18/400-mtd-spinand-Support-fmsh.patch index 6ea49f7c5f..b332761e1e 100644 --- a/target/linux/generic/hack-6.18/400-mtd-spinand-Support-fmsh.patch +++ b/target/linux/generic/hack-6.18/400-mtd-spinand-Support-fmsh.patch @@ -17,7 +17,7 @@ Signed-off-by: Jon Lin --- a/drivers/mtd/nand/spi/fmsh.c +++ b/drivers/mtd/nand/spi/fmsh.c -@@ -188,6 +188,115 @@ static const struct mtd_ooblayout_ops fm +@@ -187,6 +187,115 @@ static const struct mtd_ooblayout_ops fm .free = fm25s01bi3_ooblayout_free, }; @@ -133,7 +133,7 @@ Signed-off-by: Jon Lin static const struct spinand_info fmsh_spinand_table[] = { SPINAND_INFO("FM25G01B", SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd1), -@@ -228,6 +337,51 @@ static const struct spinand_info fmsh_sp +@@ -227,6 +336,51 @@ static const struct spinand_info fmsh_sp SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&fm25s01bi3_ooblayout, fm25s01bi3_ecc_get_status)), diff --git a/target/linux/sunxi/cortexa53/config-6.18 b/target/linux/sunxi/cortexa53/config-6.18 index 85bc0232c4..5997a93a30 100644 --- a/target/linux/sunxi/cortexa53/config-6.18 +++ b/target/linux/sunxi/cortexa53/config-6.18 @@ -45,6 +45,7 @@ CONFIG_ARM_SMMU_MMU_500_CPRE_ERRATA=y CONFIG_ARM_SMMU_V3=y # CONFIG_ARM_SMMU_V3_SVA is not set CONFIG_AUDIT_ARCH_COMPAT_GENERIC=y +CONFIG_AW_INNO_COMBOPHY=y CONFIG_BUILTIN_RETURN_ADDRESS_STRIPS_PAC=y CONFIG_CC_HAVE_SHADOW_CALL_STACK=y CONFIG_CC_HAVE_STACKPROTECTOR_SYSREG=y @@ -63,8 +64,8 @@ CONFIG_CRYPTO_SHA1_ARM64_CE=y CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC=y CONFIG_DMA_DIRECT_REMAP=y CONFIG_DMA_NEED_SYNC=y -CONFIG_DWMAC_SUN8I=y CONFIG_DWMAC_SUN55I=y +CONFIG_DWMAC_SUN8I=y CONFIG_EXCLUSIVE_SYSTEM_RAM=y CONFIG_FRAME_POINTER=y CONFIG_FUNCTION_ALIGNMENT=4 @@ -78,6 +79,7 @@ CONFIG_GENERIC_MSI_IRQ=y CONFIG_HAS_IOPORT=y CONFIG_HOTPLUG_CORE_SYNC=y CONFIG_HOTPLUG_CORE_SYNC_DEAD=y +# CONFIG_HOTPLUG_PCI_PCIE is not set CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000 # CONFIG_IOMMU_DEFAULT_DMA_LAZY is not set # CONFIG_IOMMU_DEFAULT_DMA_STRICT is not set @@ -98,6 +100,22 @@ CONFIG_NO_IOPORT_MAP=y CONFIG_PAGE_SIZE_LESS_THAN_256KB=y CONFIG_PAGE_SIZE_LESS_THAN_64KB=y CONFIG_PARTITION_PERCPU=y +CONFIG_PCI=y +CONFIG_PCIEAER=y +CONFIG_PCIEASPM=y +CONFIG_PCIEASPM_DEFAULT=y +# CONFIG_PCIEASPM_PERFORMANCE is not set +# CONFIG_PCIEASPM_POWERSAVE is not set +# CONFIG_PCIEASPM_POWER_SUPERSAVE is not set +CONFIG_PCIEPORTBUS=y +# CONFIG_PCIE_DW_DEBUGFS is not set +CONFIG_PCIE_DW_PLAT_HOST=y +# CONFIG_PCIE_SUN55I_NONE is not set +CONFIG_PCIE_SUN55I_RC=y +CONFIG_PCI_DOMAINS=y +CONFIG_PCI_DOMAINS_GENERIC=y +CONFIG_PCI_HOST_GENERIC=y +CONFIG_PCI_MSI=y CONFIG_PER_VMA_LOCK=y CONFIG_PHY_SUN50I_USB3=y CONFIG_PINCTRL_SUN50I_A100=y @@ -140,6 +158,7 @@ CONFIG_SUN50I_IOMMU=y CONFIG_SUN55I_A523_CCU=y CONFIG_SUN55I_A523_MCU_CCU=y CONFIG_SUN55I_A523_R_CCU=y +CONFIG_SUN55I_IOMMU=y CONFIG_SUN55I_PCK600=y CONFIG_SUN6I_RTC_CCU=y CONFIG_SUN6I_R_INTC=y @@ -147,14 +166,16 @@ CONFIG_SUNXI_NMI_INTC=y CONFIG_SURFACE_PLATFORMS=y CONFIG_SYSCTL_EXCEPTION_TRACE=y CONFIG_THREAD_INFO_IN_TASK=y -# CONFIG_UACCE is not set CONFIG_TOOLS_SUPPORT_RELR=y CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y +# CONFIG_UACCE is not set CONFIG_UNMAP_KERNEL_AT_EL0=y CONFIG_USB_MUSB_DUAL_ROLE=y CONFIG_USB_MUSB_HDRC=y CONFIG_USB_MUSB_SUNXI=y CONFIG_USB_PHY=y +CONFIG_USB_XHCI_HCD=y +CONFIG_USB_XHCI_PLATFORM=y CONFIG_USER_STACKTRACE_SUPPORT=y CONFIG_VDSO_GETRANDOM=y CONFIG_VMAP_STACK=y diff --git a/target/linux/sunxi/cortexa53/target.mk b/target/linux/sunxi/cortexa53/target.mk index 6d5172c5bd..d73227ba7d 100644 --- a/target/linux/sunxi/cortexa53/target.mk +++ b/target/linux/sunxi/cortexa53/target.mk @@ -8,4 +8,4 @@ ARCH:=aarch64 BOARDNAME:=Allwinner A64/H5/H6/H616/H618 CPU_TYPE:=cortex-a53 KERNELNAME:=Image dtbs -FEATURES+=fpu \ No newline at end of file +FEATURES+=fpu pci pcie diff --git a/target/linux/sunxi/patches-6.18/121-clk-a523-add-initial-cpufreq-support.patch b/target/linux/sunxi/patches-6.18/121-01-clk-a523-add-initial-cpufreq-support.patch similarity index 100% rename from target/linux/sunxi/patches-6.18/121-clk-a523-add-initial-cpufreq-support.patch rename to target/linux/sunxi/patches-6.18/121-01-clk-a523-add-initial-cpufreq-support.patch diff --git a/target/linux/sunxi/patches-6.18/121-02-clk-gate-add-regmap-gate-support.patch b/target/linux/sunxi/patches-6.18/121-02-clk-gate-add-regmap-gate-support.patch new file mode 100644 index 0000000000..6bfb077d85 --- /dev/null +++ b/target/linux/sunxi/patches-6.18/121-02-clk-gate-add-regmap-gate-support.patch @@ -0,0 +1,244 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andre Przywara +Date: Fri, 10 Jun 2022 18:20:29 +0100 +Subject: clk: gate: add support for regmap based gates + +While we have nice wrappers for simple bit-flip MMIO based clock gates, +a single bit to toggle in a regmap still requires to write a lot of clock +framework boilerplate. + +Support generic wrappers for regmap based clock gates, by adding them to +the existing clock-gates.c file. Since a read-modify-write operation in a +regmap can be much more complex than a readl/writel pair, we cannot use +the .enable/.disable ops members, but do the actual flipping already in +.prepare/.unprepare, where we can sleep. Also we cannot provide an +.is_enabled function, since this must not sleep as well. +On the upside all the locking for the r/m/w operation is provided by +regmap already, so we can skip that. +The rest of the CCF boilerplate code can be shared. + +Signed-off-by: Andre Przywara +--- + drivers/clk/clk-gate.c | 60 +++++++++- + include/linux/clk-provider.h | 36 +++++- + 2 files changed, 89 insertions(+), 7 deletions(-) + +--- a/drivers/clk/clk-gate.c ++++ b/drivers/clk/clk-gate.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -124,11 +125,42 @@ const struct clk_ops clk_gate_ops = { + }; + EXPORT_SYMBOL_GPL(clk_gate_ops); + ++static int clk_gate_regmap_setclrbit(struct clk_hw *hw, bool enable) ++{ ++ struct clk_gate *gate = to_clk_gate(hw); ++ bool set = gate->flags & CLK_GATE_SET_TO_DISABLE; ++ ++ set ^= enable; ++ ++ if (set) ++ return regmap_set_bits(gate->regmap, gate->regmap_offs, ++ BIT(gate->bit_idx)); ++ else ++ return regmap_clear_bits(gate->regmap, gate->regmap_offs, ++ BIT(gate->bit_idx)); ++} ++ ++static int clk_gate_regmap_prepare(struct clk_hw *hw) ++{ ++ return clk_gate_regmap_setclrbit(hw, true); ++} ++ ++static void clk_gate_regmap_unprepare(struct clk_hw *hw) ++{ ++ clk_gate_regmap_setclrbit(hw, false); ++} ++ ++const struct clk_ops clk_gate_regmap_ops = { ++ .prepare = clk_gate_regmap_prepare, ++ .unprepare = clk_gate_regmap_unprepare, ++}; ++ + struct clk_hw *__clk_hw_register_gate(struct device *dev, + struct device_node *np, const char *name, + const char *parent_name, const struct clk_hw *parent_hw, + const struct clk_parent_data *parent_data, + unsigned long flags, ++ struct regmap *regmap, unsigned int regmap_offs, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock) + { +@@ -150,7 +182,10 @@ struct clk_hw *__clk_hw_register_gate(st + return ERR_PTR(-ENOMEM); + + init.name = name; +- init.ops = &clk_gate_ops; ++ if (regmap) ++ init.ops = &clk_gate_regmap_ops; ++ else ++ init.ops = &clk_gate_ops; + init.flags = flags; + init.parent_names = parent_name ? &parent_name : NULL; + init.parent_hws = parent_hw ? &parent_hw : NULL; +@@ -162,6 +197,8 @@ struct clk_hw *__clk_hw_register_gate(st + + /* struct clk_gate assignments */ + gate->reg = reg; ++ gate->regmap = regmap; ++ gate->regmap_offs = regmap_offs; + gate->bit_idx = bit_idx; + gate->flags = clk_gate_flags; + gate->lock = lock; +@@ -197,6 +234,22 @@ struct clk *clk_register_gate(struct dev + } + EXPORT_SYMBOL_GPL(clk_register_gate); + ++struct clk *clk_register_regmap_gate(struct device *dev, const char *name, ++ const char *parent_name, unsigned long flags, ++ struct regmap *regmap, unsigned int regmap_offs, ++ u8 bit_idx, u8 clk_gate_flags) ++{ ++ struct clk_hw *hw; ++ ++ hw = clk_hw_register_regmap_gate(dev, name, parent_name, flags, regmap, ++ regmap_offs, bit_idx, clk_gate_flags); ++ ++ if (IS_ERR(hw)) ++ return ERR_CAST(hw); ++ return hw->clk; ++} ++EXPORT_SYMBOL_GPL(clk_register_regmap_gate); ++ + void clk_unregister_gate(struct clk *clk) + { + struct clk_gate *gate; +@@ -234,6 +287,7 @@ struct clk_hw *__devm_clk_hw_register_ga + const char *parent_name, const struct clk_hw *parent_hw, + const struct clk_parent_data *parent_data, + unsigned long flags, ++ struct regmap *regmap, unsigned int regmap_offs, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock) + { +@@ -244,8 +298,8 @@ struct clk_hw *__devm_clk_hw_register_ga + return ERR_PTR(-ENOMEM); + + hw = __clk_hw_register_gate(dev, np, name, parent_name, parent_hw, +- parent_data, flags, reg, bit_idx, +- clk_gate_flags, lock); ++ parent_data, flags, regmap, regmap_offs, ++ reg, bit_idx, clk_gate_flags, lock); + + if (!IS_ERR(hw)) { + *ptr = hw; +--- a/include/linux/clk-provider.h ++++ b/include/linux/clk-provider.h +@@ -8,6 +8,7 @@ + + #include + #include ++#include + + /* + * flags used across common struct clk. these flags should only affect the +@@ -526,6 +527,8 @@ void of_fixed_clk_setup(struct device_no + struct clk_gate { + struct clk_hw hw; + void __iomem *reg; ++ struct regmap *regmap; ++ unsigned int regmap_offs; + u8 bit_idx; + u8 flags; + spinlock_t *lock; +@@ -543,6 +546,7 @@ struct clk_hw *__clk_hw_register_gate(st + const char *parent_name, const struct clk_hw *parent_hw, + const struct clk_parent_data *parent_data, + unsigned long flags, ++ struct regmap *regmap, unsigned int regmap_offs, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock); + struct clk_hw *__devm_clk_hw_register_gate(struct device *dev, +@@ -550,12 +554,17 @@ struct clk_hw *__devm_clk_hw_register_ga + const char *parent_name, const struct clk_hw *parent_hw, + const struct clk_parent_data *parent_data, + unsigned long flags, ++ struct regmap *regmap, unsigned int regmap_offs, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock); + struct clk *clk_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + void __iomem *reg, u8 bit_idx, + u8 clk_gate_flags, spinlock_t *lock); ++struct clk *clk_register_regmap_gate(struct device *dev, const char *name, ++ const char *parent_name, unsigned long flags, ++ struct regmap *regmap, unsigned int regmap_offs, ++ u8 bit_idx, u8 clk_gate_flags); + /** + * clk_hw_register_gate - register a gate clock with the clock framework + * @dev: device that is registering this clock +@@ -570,8 +579,14 @@ struct clk *clk_register_gate(struct dev + #define clk_hw_register_gate(dev, name, parent_name, flags, reg, bit_idx, \ + clk_gate_flags, lock) \ + __clk_hw_register_gate((dev), NULL, (name), (parent_name), NULL, \ +- NULL, (flags), (reg), (bit_idx), \ ++ NULL, (flags), NULL, 0, (reg), (bit_idx), \ + (clk_gate_flags), (lock)) ++ ++#define clk_hw_register_regmap_gate(dev, name, parent_name, flags, regmap, \ ++ regmap_offs, bit_idx, clk_gate_flags) \ ++ __clk_hw_register_gate((dev), NULL, (name), (parent_name), NULL, \ ++ NULL, (flags), regmap, regmap_offs, NULL, \ ++ (bit_idx), (clk_gate_flags), NULL) + /** + * clk_hw_register_gate_parent_hw - register a gate clock with the clock + * framework +@@ -587,8 +602,15 @@ struct clk *clk_register_gate(struct dev + #define clk_hw_register_gate_parent_hw(dev, name, parent_hw, flags, reg, \ + bit_idx, clk_gate_flags, lock) \ + __clk_hw_register_gate((dev), NULL, (name), NULL, (parent_hw), \ +- NULL, (flags), (reg), (bit_idx), \ ++ NULL, (flags), NULL, 0, (reg), (bit_idx), \ + (clk_gate_flags), (lock)) ++ ++#define clk_hw_register_regmap_gate_parent_hw(dev, name, parent_hw, flags, \ ++ regmap, regmap_offs, bit_idx, \ ++ clk_gate_flags) \ ++ __clk_hw_register_gate((dev), NULL, (name), NULL, (parent_hw), \ ++ NULL, (flags), regmap, regmap_offs, NULL, \ ++ (bit_idx), (clk_gate_flags), NULL) + /** + * clk_hw_register_gate_parent_data - register a gate clock with the clock + * framework +@@ -604,7 +626,7 @@ struct clk *clk_register_gate(struct dev + #define clk_hw_register_gate_parent_data(dev, name, parent_data, flags, reg, \ + bit_idx, clk_gate_flags, lock) \ + __clk_hw_register_gate((dev), NULL, (name), NULL, NULL, (parent_data), \ +- (flags), (reg), (bit_idx), \ ++ (flags), NULL, 0, (reg), (bit_idx), \ + (clk_gate_flags), (lock)) + /** + * devm_clk_hw_register_gate - register a gate clock with the clock framework +@@ -620,8 +642,14 @@ struct clk *clk_register_gate(struct dev + #define devm_clk_hw_register_gate(dev, name, parent_name, flags, reg, bit_idx,\ + clk_gate_flags, lock) \ + __devm_clk_hw_register_gate((dev), NULL, (name), (parent_name), NULL, \ +- NULL, (flags), (reg), (bit_idx), \ ++ NULL, (flags), NULL, 0, (reg), (bit_idx), \ + (clk_gate_flags), (lock)) ++#define devm_clk_hw_register_regmap_gate(dev, name, parent_name, flags, \ ++ regmap, regmap_offs, bit_idx, \ ++ clk_gate_flags) \ ++ __devm_clk_hw_register_gate((dev), NULL, (name), (parent_name), NULL, \ ++ NULL, (flags), (regmap), (regmap_offs), NULL, \ ++ (bit_idx), (clk_gate_flags), NULL) + /** + * devm_clk_hw_register_gate_parent_hw - register a gate clock with the clock + * framework diff --git a/target/linux/sunxi/patches-6.18/121-03-clk-sunxi-ng-fix-clock-handling-for-ccu-sun55i-a523.patch b/target/linux/sunxi/patches-6.18/121-03-clk-sunxi-ng-fix-clock-handling-for-ccu-sun55i-a523.patch new file mode 100644 index 0000000000..575442a28d --- /dev/null +++ b/target/linux/sunxi/patches-6.18/121-03-clk-sunxi-ng-fix-clock-handling-for-ccu-sun55i-a523.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marvin Wewer +Date: Tue, 9 Dec 2025 16:43:16 +0000 +Subject: clk: sunxi-ng: add A523 USB3 ref clock and reset + +Signed-off-by: Marvin Wewer +--- + drivers/clk/sunxi-ng/ccu-sun55i-a523.c | 13 +++++++++- + include/dt-bindings/clock/sun55i-a523-ccu.h | 1 + + 2 files changed, 13 insertions(+), 1 deletion(-) + +--- a/drivers/clk/sunxi-ng/ccu-sun55i-a523.c ++++ b/drivers/clk/sunxi-ng/ccu-sun55i-a523.c +@@ -1186,6 +1186,15 @@ static SUNXI_CCU_MUX_DATA_WITH_GATE(fano + BIT(23), /* gate */ + 0); + ++static const struct clk_parent_data usb3_ref_parents[] = { { .fw_name = "hosc" }, { .hw = &pll_periph0_200M_clk.hw }, { .hw = &pll_periph1_200M_clk.hw } }; ++static SUNXI_CCU_M_DATA_WITH_MUX_GATE(usb3_ref_clk, "usb3-ref", ++ usb3_ref_parents, 0x0A84, ++ 0, 5, /* M */ ++ 24, 3, /* mux */ ++ BIT(31), /* gate */ ++ 0); ++ ++ + /* + * Contains all clocks that are controlled by a hardware register. They + * have a (sunxi) .common member, which needs to be initialised by the common +@@ -1354,6 +1363,7 @@ static struct ccu_common *sun55i_a523_cc + &fanout0_clk.common, + &fanout1_clk.common, + &fanout2_clk.common, ++ &usb3_ref_clk.common, + }; + + static struct clk_hw_onecell_data sun55i_a523_hw_clks = { +@@ -1538,8 +1548,9 @@ static struct clk_hw_onecell_data sun55i + [CLK_FANOUT1] = &fanout1_clk.common.hw, + [CLK_FANOUT2] = &fanout2_clk.common.hw, + [CLK_NPU] = &npu_clk.common.hw, ++ [CLK_USB3_REF] = &usb3_ref_clk.common.hw, + }, +- .num = CLK_NPU + 1, ++ .num = CLK_USB3_REF + 1, + }; + + static struct ccu_reset_map sun55i_a523_ccu_resets[] = { +--- a/include/dt-bindings/clock/sun55i-a523-ccu.h ++++ b/include/dt-bindings/clock/sun55i-a523-ccu.h +@@ -186,5 +186,6 @@ + #define CLK_FANOUT1 177 + #define CLK_FANOUT2 178 + #define CLK_NPU 179 ++#define CLK_USB3_REF 180 + + #endif /* _DT_BINDINGS_CLK_SUN55I_A523_CCU_H_ */ diff --git a/target/linux/sunxi/patches-6.18/124-iommu-sunxi-add-iommu-driver.patch b/target/linux/sunxi/patches-6.18/124-iommu-sunxi-add-iommu-driver.patch new file mode 100644 index 0000000000..6aa0ba460e --- /dev/null +++ b/target/linux/sunxi/patches-6.18/124-iommu-sunxi-add-iommu-driver.patch @@ -0,0 +1,2363 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marvin Wewer +Date: Mon, 25 Aug 2025 10:45:25 +0000 +Subject: iommu: sunxi: add sun55i (A523) IOMMU driver + +Signed-off-by: Marvin Wewer +--- + drivers/iommu/Kconfig | 10 + + drivers/iommu/Makefile | 3 + + drivers/iommu/sun55i-iommu-pgtable.c | 468 +++ + drivers/iommu/sun55i-iommu-pgtable.h | 125 + + drivers/iommu/sun55i-iommu.c | 1604 ++++++++++ + drivers/iommu/sun55i-iommu.h | 57 + + include/sunxi-iommu.h | 50 + + 7 files changed, 2317 insertions(+) + +--- a/drivers/iommu/Kconfig ++++ b/drivers/iommu/Kconfig +@@ -245,6 +245,16 @@ config SUN50I_IOMMU + help + Support for the IOMMU introduced in the Allwinner H6 SoCs. + ++config SUN55I_IOMMU ++ bool "Allwinner A523 IOMMU Support" ++ depends on HAS_DMA ++ depends on ARCH_SUNXI || COMPILE_TEST ++ select ARM_DMA_USE_IOMMU ++ select IOMMU_API ++ select IOMMU_DMA ++ help ++ Support for the IOMMU introduced in the Allwinner A523 SoCs. ++ + config TEGRA_IOMMU_SMMU + bool "NVIDIA Tegra SMMU Support" + depends on ARCH_TEGRA +--- a/drivers/iommu/Makefile ++++ b/drivers/iommu/Makefile +@@ -34,3 +34,6 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o + obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o + obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o + obj-$(CONFIG_APPLE_DART) += apple-dart.o ++obj-$(CONFIG_SUN55I_IOMMU) += sunxi-iommu.o ++sunxi-iommu-objs := sun55i-iommu-pgtable.o ++sunxi-iommu-objs += sun55i-iommu.o +--- /dev/null ++++ b/drivers/iommu/sun55i-iommu-pgtable.c +@@ -0,0 +1,468 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Allwinner's pgtable controler ++ * ++ * Copyright (c) 2023, ouyangkun ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ * ++ */ ++ ++#include ++#include ++#include "sun55i-iommu.h" ++#include ++ ++#define NUM_ENTRIES_PDE (1UL << (IOMMU_VA_BITS - IOMMU_PD_SHIFT)) ++#define NUM_ENTRIES_PTE (1UL << (IOMMU_PD_SHIFT - IOMMU_PT_SHIFT)) ++#define PD_SIZE (NUM_ENTRIES_PDE * sizeof(u32)) ++#define PT_SIZE (NUM_ENTRIES_PTE * sizeof(u32)) ++ ++#define PAGE_OFFSET_MASK ((1UL << IOMMU_PT_SHIFT) - 1) ++#define IOPTE_BASE_MASK (~(PT_SIZE - 1)) ++ ++/* ++ * Page Directory Entry Control Bits ++ */ ++#define DENT_VALID 0x01 ++#define DENT_PTE_SHFIT 10 ++#define DENT_WRITABLE BIT(3) ++#define DENT_READABLE BIT(2) ++ ++/* ++ * Page Table Entry Control Bits ++ */ ++#define SUNXI_PTE_PAGE_WRITABLE BIT(3) ++#define SUNXI_PTE_PAGE_READABLE BIT(2) ++#define SUNXI_PTE_PAGE_VALID BIT(1) ++ ++#define IS_VALID(x) (((x)&0x03) == DENT_VALID) ++ ++#define IOPDE_INDEX(va) (((va) >> IOMMU_PD_SHIFT) & (NUM_ENTRIES_PDE - 1)) ++#define IOPTE_INDEX(va) (((va) >> IOMMU_PT_SHIFT) & (NUM_ENTRIES_PTE - 1)) ++ ++#define IOPTE_BASE(ent) ((ent)&IOPTE_BASE_MASK) ++ ++#define IOPTE_TO_PFN(ent) ((*ent) & IOMMU_PT_MASK) ++#define IOVA_PAGE_OFT(va) ((va)&PAGE_OFFSET_MASK) ++ ++/* IO virtual address start page frame number */ ++#define IOVA_START_PFN (1) ++#define IOVA_PFN(addr) ((addr) >> PAGE_SHIFT) ++ ++/* TLB Invalid ALIGN */ ++#define IOVA_4M_ALIGN(iova) ((iova) & (~0x3fffff)) ++ ++struct sunxi_pgtable_t { ++ unsigned int *pgtable; ++ struct kmem_cache *iopte_cache; ++ struct device *dma_dev; ++} sunxi_pgtable_params; ++ ++/* pointer to l1 table entry */ ++static inline u32 *iopde_offset(u32 *iopd, dma_addr_t iova) ++{ ++ return iopd + IOPDE_INDEX(iova); ++} ++ ++/* pointer to l2 table entry */ ++static inline u32 *iopte_offset(u32 *ent, dma_addr_t iova) ++{ ++ u64 iopte_base = 0; ++ ++ iopte_base = IOPTE_BASE(*ent); ++ iopte_base = iommu_phy_to_cpu_phy(iopte_base); ++ ++ return (u32 *)__va(iopte_base) + IOPTE_INDEX(iova); ++} ++ ++static int sunxi_alloc_iopte(u32 *sent, int prot) ++{ ++ u32 *pent; ++ u32 flags = 0; ++ ++ flags |= (prot & IOMMU_READ) ? DENT_READABLE : 0; ++ flags |= (prot & IOMMU_WRITE) ? DENT_WRITABLE : 0; ++ ++ pent = kmem_cache_zalloc(sunxi_pgtable_params.iopte_cache, GFP_ATOMIC); ++ WARN_ON((unsigned long)pent & (PT_SIZE - 1)); ++ if (!pent) { ++ pr_err("%s, %d, kmalloc failed!\n", __func__, __LINE__); ++ return 0; ++ } ++ dma_sync_single_for_cpu(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(sent), sizeof(*sent), ++ DMA_TO_DEVICE); ++ *sent = cpu_phy_to_iommu_phy(__pa(pent)) | DENT_VALID; ++ dma_sync_single_for_device(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(sent), sizeof(*sent), ++ DMA_TO_DEVICE); ++ ++ return 1; ++} ++ ++static void sunxi_free_iopte(u32 *pent) ++{ ++ kmem_cache_free(sunxi_pgtable_params.iopte_cache, pent); ++} ++ ++static inline u32 sunxi_mk_pte(phys_addr_t page, int prot) ++{ ++ u32 flags = 0; ++ u32 high_addr = 0; ++ ++ flags |= (prot & IOMMU_READ) ? SUNXI_PTE_PAGE_READABLE : 0; ++ flags |= (prot & IOMMU_WRITE) ? SUNXI_PTE_PAGE_WRITABLE : 0; ++ page &= IOMMU_PT_MASK; ++ ++ return page | high_addr | flags | SUNXI_PTE_PAGE_VALID; ++} ++ ++int sunxi_pgtable_prepare_l1_tables(unsigned int *pgtable, ++ dma_addr_t iova_start, dma_addr_t iova_end, ++ int prot) ++{ ++ u32 *dent; ++ for (; iova_start <= iova_end; iova_start += SPD_SIZE) { ++ dent = iopde_offset(pgtable, iova_start); ++ if (!IS_VALID(*dent) && !sunxi_alloc_iopte(dent, prot)) { ++ return -ENOMEM; ++ } ++ } ++ return 0; ++} ++ ++int sunxi_pgtable_prepare_l2_tables(unsigned int *pgtable, ++ dma_addr_t iova_start, dma_addr_t iova_end, ++ phys_addr_t paddr, int prot) ++{ ++ size_t paddr_start; ++ u32 *dent, *pent; ++ u32 iova_tail_count, iova_tail_size; ++ u32 pent_val; ++ int i; ++ paddr = cpu_phy_to_iommu_phy(paddr); ++ paddr_start = paddr & IOMMU_PT_MASK; ++ for (; iova_start < iova_end;) { ++ iova_tail_count = NUM_ENTRIES_PTE - IOPTE_INDEX(iova_start); ++ iova_tail_size = iova_tail_count * SPAGE_SIZE; ++ if (iova_start + iova_tail_size > iova_end) { ++ iova_tail_size = iova_end - iova_start; ++ iova_tail_count = iova_tail_size / SPAGE_SIZE; ++ } ++ ++ dent = iopde_offset(pgtable, iova_start); ++ pent = iopte_offset(dent, iova_start); ++ pent_val = sunxi_mk_pte(paddr_start, prot); ++ for (i = 0; i < iova_tail_count; i++) { ++ WARN_ON(*pent); ++ *pent = pent_val + SPAGE_SIZE * i; ++ pent++; ++ } ++ ++ dma_sync_single_for_device( ++ sunxi_pgtable_params.dma_dev, ++ virt_to_phys(iopte_offset(dent, iova_start)), ++ iova_tail_count << 2, DMA_TO_DEVICE); ++ iova_start += iova_tail_size; ++ paddr_start += iova_tail_size; ++ } ++ return 0; ++} ++ ++ ++int sunxi_pgtable_delete_l2_tables(unsigned int *pgtable, dma_addr_t iova_start, ++ dma_addr_t iova_end) ++{ ++ u32 *dent, *pent; ++ u32 iova_tail_count, iova_tail_size; ++ iova_tail_count = NUM_ENTRIES_PTE - IOPTE_INDEX(iova_start); ++ iova_tail_size = iova_tail_count * SPAGE_SIZE; ++ if (iova_start + iova_tail_size > iova_end) { ++ iova_tail_size = iova_end - iova_start; ++ iova_tail_count = iova_tail_size / SPAGE_SIZE; ++ } ++ ++ dent = iopde_offset(pgtable, iova_start); ++ if (!IS_VALID(*dent)) ++ return -EINVAL; ++ pent = iopte_offset(dent, iova_start); ++ memset(pent, 0, iova_tail_count * sizeof(u32)); ++ dma_sync_single_for_device(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(iopte_offset(dent, iova_start)), ++ iova_tail_count << 2, DMA_TO_DEVICE); ++ ++ if (iova_tail_size == SPD_SIZE) { ++ *dent = 0; ++ dma_sync_single_for_device(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(dent), sizeof(*dent), ++ DMA_TO_DEVICE); ++ sunxi_free_iopte(pent); ++ } ++ return iova_tail_size; ++} ++ ++ ++phys_addr_t sunxi_pgtable_iova_to_phys(unsigned int *pgtable, dma_addr_t iova) ++{ ++ u32 *dent, *pent; ++ phys_addr_t ret = 0; ++ dent = iopde_offset(pgtable, iova); ++ if (IS_VALID(*dent)) { ++ pent = iopte_offset(dent, iova); ++ if (*pent) { ++ ret = IOPTE_TO_PFN(pent) + IOVA_PAGE_OFT(iova); ++ ret = iommu_phy_to_cpu_phy(ret); ++ } ++ } ++ return ret; ++} ++ ++ ++int sunxi_pgtable_invalid_helper(unsigned int *pgtable, dma_addr_t iova) ++{ ++ u32 *pte_addr, *dte_addr; ++ ++ dte_addr = iopde_offset(pgtable, iova); ++ if ((*dte_addr & 0x3) != 0x1) { ++ pr_err("0x%pad is not mapped!\n", &iova); ++ return 1; ++ } ++ pte_addr = iopte_offset(dte_addr, iova); ++ if ((*pte_addr & 0x2) == 0) { ++ pr_err("0x%pad is not mapped!\n", &iova); ++ return 1; ++ } ++ pr_err("0x%pad is mapped!\n", &iova); ++ ++ return 0; ++} ++ ++ ++void sunxi_pgtable_clear(unsigned int *pgtable) ++{ ++ int i = 0; ++ u32 *dent, *pent; ++ size_t iova; ++ ++ for (i = 0; i < NUM_ENTRIES_PDE; ++i) { ++ dent = pgtable + i; ++ iova = (unsigned long)i << IOMMU_PD_SHIFT; ++ if (IS_VALID(*dent)) { ++ pent = iopte_offset(dent, iova); ++ dma_sync_single_for_cpu(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(pent), PT_SIZE, ++ DMA_TO_DEVICE); ++ memset(pent, 0, PT_SIZE); ++ dma_sync_single_for_device(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(pent), PT_SIZE, ++ DMA_TO_DEVICE); ++ dma_sync_single_for_cpu(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(dent), PT_SIZE, ++ DMA_TO_DEVICE); ++ *dent = 0; ++ dma_sync_single_for_device(sunxi_pgtable_params.dma_dev, ++ virt_to_phys(dent), ++ sizeof(*dent), ++ DMA_TO_DEVICE); ++ sunxi_free_iopte(pent); ++ } ++ } ++} ++ ++ ++unsigned int *sunxi_pgtable_alloc(void) ++{ ++ unsigned int *pgtable; ++ pgtable = (unsigned int *)__get_free_pages(GFP_KERNEL, ++ get_order(PD_SIZE)); ++ ++ if (pgtable != NULL) { ++ memset(pgtable, 0, PD_SIZE); ++ } ++ sunxi_pgtable_params.pgtable = pgtable; ++ return pgtable; ++} ++ ++ ++void sunxi_pgtable_free(unsigned int *pgtable) ++{ ++ free_pages((unsigned long)pgtable, get_order(PD_SIZE)); ++ sunxi_pgtable_params.pgtable = NULL; ++} ++ ++ ++static inline bool __region_ended(u32 pent) ++{ ++ return !(pent & SUNXI_PTE_PAGE_VALID); ++} ++ ++static inline bool __access_mask_changed(u32 pent, u32 old_mask) ++{ ++ return old_mask != ++ (pent & (SUNXI_PTE_PAGE_READABLE | SUNXI_PTE_PAGE_WRITABLE)); ++} ++ ++static u32 __print_region(char *buf, size_t buf_len, ssize_t len, ++ struct dump_region *active_region, ++ bool for_sysfs_show) ++{ ++ if (active_region->type == DUMP_REGION_RESERVE) { ++ if (for_sysfs_show) { ++ len += sysfs_emit_at( ++ buf, len, ++ "iova:%pad size:0x%zx\n", ++ &active_region->iova, active_region->size); ++ } else { ++ len += scnprintf( ++ buf + len, buf_len - len, ++ "iova:%pad size:0x%zx\n", ++ &active_region->iova, active_region->size); ++ } ++ } else { ++ if (for_sysfs_show) { ++ len += sysfs_emit_at( ++ buf, len, ++ "iova:%pad phys:%pad %s%s size:0x%zx\n", ++ &active_region->iova, &active_region->phys, ++ active_region->access_mask & ++ SUNXI_PTE_PAGE_READABLE ? ++ "R" : ++ " ", ++ active_region->access_mask & ++ SUNXI_PTE_PAGE_WRITABLE ? ++ "W" : ++ " ", ++ active_region->size); ++ } else { ++ len += scnprintf( ++ buf + len, buf_len - len, ++ "iova:%pad phys:%pad %s%s size:0x%zx\n", ++ &active_region->iova, &active_region->phys, ++ active_region->access_mask & ++ SUNXI_PTE_PAGE_READABLE ? ++ "R" : ++ " ", ++ active_region->access_mask & ++ SUNXI_PTE_PAGE_WRITABLE ? ++ "W" : ++ " ", ++ active_region->size); ++ } ++ } ++ return len; ++} ++ ++ssize_t sunxi_pgtable_dump(unsigned int *pgtable, ssize_t len, char *buf, ++ size_t buf_len, bool for_sysfs_show) ++{ ++ /* walk and dump */ ++ int i, j; ++ u32 *dent, *pent; ++ struct dump_region active_region; ++ ++ if (for_sysfs_show) { ++ len += sysfs_emit_at(buf, len, "mapped\n"); ++ } else { ++ len += scnprintf(buf + len, buf_len - len, "mapped\n"); ++ } ++ ++ dent = pgtable; ++ active_region.type = DUMP_REGION_MAP; ++ active_region.size = 0; ++ active_region.access_mask = 0; ++ for (i = 0; i < NUM_ENTRIES_PDE; i++) { ++ j = 0; ++ if (!IS_VALID(dent[i])) { ++ /* empty dentry measn ended of region, print it*/ ++ if (active_region.size) { ++ len = __print_region(buf, buf_len, len, ++ &active_region, ++ for_sysfs_show); ++ /* prepare next region */ ++ active_region.size = 0; ++ active_region.access_mask = 0; ++ } ++ continue; ++ } ++ /* iova here use for l1 idx, safe to pass 0 to get entry for 1st page(idx 0)*/ ++ pent = iopte_offset(dent + i, 0); ++ for (; j < NUM_ENTRIES_PTE; j++) { ++ if (active_region.size) { ++ /* looks like we are counting something, check if it need printing */ ++ if (__region_ended(pent[j]) /* not contiguous */ ++ || ++ (active_region.access_mask && ++ __access_mask_changed( ++ pent[j], ++ active_region ++ .access_mask)) /* different access */ ++ ) { ++ len = __print_region(buf, buf_len, len, ++ &active_region, ++ for_sysfs_show); ++ ++ /* prepare next region */ ++ active_region.size = 0; ++ active_region.access_mask = 0; ++ } ++ } ++ ++ if (pent[j] & SUNXI_PTE_PAGE_VALID) { ++ /* no on count region, mark start address */ ++ if (active_region.size == 0) { ++ active_region.iova = ++ ((dma_addr_t)i ++ << IOMMU_PD_SHIFT) + ++ ((dma_addr_t)j ++ << IOMMU_PT_SHIFT); ++ active_region.phys = ++ iommu_phy_to_cpu_phy( ++ IOPTE_TO_PFN(&pent[j])); ++ active_region.access_mask = ++ (pent[j] & ++ (SUNXI_PTE_PAGE_READABLE | ++ SUNXI_PTE_PAGE_WRITABLE)); ++ } ++ active_region.size += 1 << IOMMU_PT_SHIFT; ++ } ++ } ++ } ++ //dump last region (if any) ++ if (active_region.size) { ++ len = __print_region(buf, buf_len, len, &active_region, ++ for_sysfs_show); ++ } ++ return len; ++} ++ ++ ++struct kmem_cache *sunxi_pgtable_alloc_pte_cache(void) ++{ ++ struct kmem_cache *cache; ++ cache = kmem_cache_create("sunxi-iopte-cache", PT_SIZE, PT_SIZE, ++ SLAB_HWCACHE_ALIGN, NULL); ++ sunxi_pgtable_params.iopte_cache = cache; ++ return cache; ++} ++ ++ ++void sunxi_pgtable_free_pte_cache(struct kmem_cache *iopte_cache) ++{ ++ kmem_cache_destroy(iopte_cache); ++} ++ ++ ++void sunxi_pgtable_set_dma_dev(struct device *dma_dev) ++{ ++ sunxi_pgtable_params.dma_dev = dma_dev; ++} +--- /dev/null ++++ b/drivers/iommu/sun55i-iommu-pgtable.h +@@ -0,0 +1,125 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Allwinner's pgtable controler ++ * ++ * Copyright (c) 2023, ouyangkun ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but WITHOUT ++ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ++ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ++ * more details. ++ * ++ */ ++#ifndef __SUNXI_IOMMU_PGTALBE__ ++#define __SUNXI_IOMMU_PGTALBE__ ++#include ++ ++#define SUNXI_PHYS_OFFSET 0x40000000UL ++ ++#define IOMMU_VA_BITS 32 ++ ++#define IOMMU_PD_SHIFT 20 ++#define IOMMU_PD_MASK (~((1UL << IOMMU_PD_SHIFT) - 1)) ++ ++#define IOMMU_PT_SHIFT 12 ++#define IOMMU_PT_MASK (~((1UL << IOMMU_PT_SHIFT) - 1)) ++ ++#define SPAGE_SIZE (1 << IOMMU_PT_SHIFT) ++#define SPD_SIZE (1 << IOMMU_PD_SHIFT) ++#define SPAGE_ALIGN(addr) ALIGN(addr, SPAGE_SIZE) ++#define SPDE_ALIGN(addr) ALIGN(addr, SPD_SIZE) ++ ++/* ++ * This version Hardware just only support 4KB page. It have ++ * a two level page table structure, where the first level has ++ * 4096 entries, and the second level has 256 entries. And, the ++ * first level is "Page Directory(PG)", every entry include a ++ * Page Table base address and a few of control bits. Second ++ * level is "Page Table(PT)", every entry include a physical ++ * page address and a few of control bits. Each entry is one ++ * 32-bit word. Most of the bits in the second level entry are ++ * used by hardware. ++ * ++ * Virtual Address Format: ++ * 31 20|19 12|11 0 ++ * +-----------------+------------+--------+ ++ * | PDE Index | PTE Index | offset | ++ * +-----------------+------------+--------+ ++ * ++ * Table Layout: ++ * ++ * First Level Second Level ++ * (Page Directory) (Page Table) ++ * ----+---------+0 ++ * ^ | PDE | ---> -+--------+---- ++ * | ----------+1 | PTE | ^ ++ * | | | +--------+ | ++ * ----------+2 | | 1K ++ * 16K | | +--------+ | ++ * ----------+3 | | v ++ * | | | +--------+---- ++ * | ---------- ++ * | | | ++ * v | | ++ * ----+--------+ ++ * ++ * IOPDE: ++ * 31 10|9 0 ++ * +------------------------+--------+ ++ * | PTE Base Address |CTRL BIT| ++ * +------------------------+--------+ ++ * ++ * IOPTE: ++ * 31 12|11 0 ++ * +---------------------+-----------+ ++ * | Phy Page Address | CTRL BIT | ++ * +---------------------+-----------+ ++ * ++ * cpu phy 0x0000 0000 ~ 0x4000 0000 is reserved for IO access, ++ * iommu phy in between 0x0000 0000 ~ 0x4000 0000 should not used ++ * as cpu phy directly, move this address space beyond iommu ++ * phy max, so iommu phys 0x0000 0000 ~ 0x4000 0000 shoule be ++ * iommu_phy_max + 0x0000 0000 ~ iommu_phy_max + 0x4000 0000(as ++ * spec said) ++ */ ++ ++static inline dma_addr_t iommu_phy_to_cpu_phy(dma_addr_t iommu_phy) ++{ ++ return iommu_phy < SUNXI_PHYS_OFFSET ? ++ iommu_phy + (1ULL << IOMMU_VA_BITS) : ++ iommu_phy; ++} ++ ++static inline dma_addr_t cpu_phy_to_iommu_phy(dma_addr_t cpu_phy) ++{ ++ return cpu_phy > (1ULL << IOMMU_VA_BITS) ? ++ cpu_phy - (1ULL << IOMMU_VA_BITS) : ++ cpu_phy; ++} ++ ++int sunxi_pgtable_prepare_l1_tables(unsigned int *pgtable, ++ dma_addr_t iova_start, dma_addr_t iova_end, ++ int prot); ++int sunxi_pgtable_prepare_l2_tables(unsigned int *pgtable, ++ dma_addr_t iova_start, dma_addr_t iova_end, ++ phys_addr_t paddr, int prot); ++int sunxi_pgtable_delete_l2_tables(unsigned int *pgtable, dma_addr_t iova_start, ++ dma_addr_t iova_end); ++phys_addr_t sunxi_pgtable_iova_to_phys(unsigned int *pgtable, dma_addr_t iova); ++int sunxi_pgtable_invalid_helper(unsigned int *pgtable, dma_addr_t iova); ++void sunxi_pgtable_clear(unsigned int *pgtable); ++unsigned int *sunxi_pgtable_alloc(void); ++void sunxi_pgtable_free(unsigned int *pgtable); ++ssize_t sunxi_pgtable_dump(unsigned int *pgtable, ssize_t len, char *buf, ++ size_t buf_len, bool for_sysfs_show); ++struct kmem_cache *sunxi_pgtable_alloc_pte_cache(void); ++void sunxi_pgtable_free_pte_cache(struct kmem_cache *iopte_cache); ++void sunxi_pgtable_set_dma_dev(struct device *dma_dev); ++ ++#endif +--- /dev/null ++++ b/drivers/iommu/sun55i-iommu.c +@@ -0,0 +1,1604 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/******************************************************************************* ++ * Copyright (C) 2016-2018, Allwinner Technology CO., LTD. ++ * Author: zhuxianbin ++ * ++ * This file is provided under a dual BSD/GPL license. When using or ++ * redistributing this file, you may do so under either license. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ ******************************************************************************/ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include "sun55i-iommu.h" ++ ++/* ++ * Register of IOMMU device ++ */ ++#define IOMMU_VERSION_REG 0x0000 ++#define IOMMU_RESET_REG 0x0010 ++#define IOMMU_ENABLE_REG 0x0020 ++#define IOMMU_BYPASS_REG 0x0030 ++#define IOMMU_AUTO_GATING_REG 0x0040 ++#define IOMMU_WBUF_CTRL_REG 0x0044 ++#define IOMMU_OOO_CTRL_REG 0x0048 ++#define IOMMU_4KB_BDY_PRT_CTRL_REG 0x004C ++#define IOMMU_TTB_REG 0x0050 ++#define IOMMU_TLB_ENABLE_REG 0x0060 ++#define IOMMU_TLB_PREFETCH_REG 0x0070 ++#define IOMMU_TLB_FLUSH_ENABLE_REG 0x0080 ++#define IOMMU_TLB_IVLD_MODE_SEL_REG 0x0084 ++#define IOMMU_TLB_IVLD_START_ADDR_REG 0x0088 ++#define IOMMU_TLB_IVLD_END_ADDR_REG 0x008C ++#define IOMMU_TLB_IVLD_ADDR_REG 0x0090 ++#define IOMMU_TLB_IVLD_ADDR_MASK_REG 0x0094 ++#define IOMMU_TLB_IVLD_ENABLE_REG 0x0098 ++#define IOMMU_PC_IVLD_MODE_SEL_REG 0x009C ++#define IOMMU_PC_IVLD_ADDR_REG 0x00A0 ++#define IOMMU_PC_IVLD_START_ADDR_REG 0x00A4 ++#define IOMMU_PC_IVLD_ENABLE_REG 0x00A8 ++#define IOMMU_PC_IVLD_END_ADDR_REG 0x00Ac ++#define IOMMU_DM_AUT_CTRL_REG0 0x00B0 ++#define IOMMU_DM_AUT_CTRL_REG1 0x00B4 ++#define IOMMU_DM_AUT_CTRL_REG2 0x00B8 ++#define IOMMU_DM_AUT_CTRL_REG3 0x00BC ++#define IOMMU_DM_AUT_CTRL_REG4 0x00C0 ++#define IOMMU_DM_AUT_CTRL_REG5 0x00C4 ++#define IOMMU_DM_AUT_CTRL_REG6 0x00C8 ++#define IOMMU_DM_AUT_CTRL_REG7 0x00CC ++#define IOMMU_DM_AUT_OVWT_REG 0x00D0 ++#define IOMMU_INT_ENABLE_REG 0x0100 ++#define IOMMU_INT_CLR_REG 0x0104 ++#define IOMMU_INT_STA_REG 0x0108 ++#define IOMMU_INT_ERR_ADDR_REG0 0x0110 ++ ++#define IOMMU_INT_ERR_ADDR_REG1 0x0114 ++#define IOMMU_INT_ERR_ADDR_REG2 0x0118 ++ ++#define IOMMU_INT_ERR_ADDR_REG3 0x011C ++#define IOMMU_INT_ERR_ADDR_REG4 0x0120 ++#define IOMMU_INT_ERR_ADDR_REG5 0x0124 ++ ++#define IOMMU_INT_ERR_ADDR_REG6 0x0128 ++#define IOMMU_INT_ERR_ADDR_REG7 0x0130 ++#define IOMMU_INT_ERR_ADDR_REG8 0x0134 ++ ++#define IOMMU_INT_ERR_DATA_REG0 0x0150 ++#define IOMMU_INT_ERR_DATA_REG1 0x0154 ++#define IOMMU_INT_ERR_DATA_REG2 0x0158 ++#define IOMMU_INT_ERR_DATA_REG3 0x015C ++#define IOMMU_INT_ERR_DATA_REG4 0x0160 ++#define IOMMU_INT_ERR_DATA_REG5 0x0164 ++ ++#define IOMMU_INT_ERR_DATA_REG6 0x0168 ++#define IOMMU_INT_ERR_DATA_REG7 0x0170 ++#define IOMMU_INT_ERR_DATA_REG8 0x0174 ++ ++#define IOMMU_L1PG_INT_REG 0x0180 ++#define IOMMU_L2PG_INT_REG 0x0184 ++#define IOMMU_VA_REG 0x0190 ++#define IOMMU_VA_DATA_REG 0x0194 ++#define IOMMU_VA_CONFIG_REG 0x0198 ++#define IOMMU_PMU_ENABLE_REG 0x0200 ++#define IOMMU_PMU_CLR_REG 0x0210 ++#define IOMMU_PMU_ACCESS_LOW_REG0 0x0230 ++#define IOMMU_PMU_ACCESS_HIGH_REG0 0x0234 ++#define IOMMU_PMU_HIT_LOW_REG0 0x0238 ++#define IOMMU_PMU_HIT_HIGH_REG0 0x023C ++#define IOMMU_PMU_ACCESS_LOW_REG1 0x0240 ++#define IOMMU_PMU_ACCESS_HIGH_REG1 0x0244 ++#define IOMMU_PMU_HIT_LOW_REG1 0x0248 ++#define IOMMU_PMU_HIT_HIGH_REG1 0x024C ++#define IOMMU_PMU_ACCESS_LOW_REG2 0x0250 ++#define IOMMU_PMU_ACCESS_HIGH_REG2 0x0254 ++#define IOMMU_PMU_HIT_LOW_REG2 0x0258 ++#define IOMMU_PMU_HIT_HIGH_REG2 0x025C ++#define IOMMU_PMU_ACCESS_LOW_REG3 0x0260 ++#define IOMMU_PMU_ACCESS_HIGH_REG3 0x0264 ++#define IOMMU_PMU_HIT_LOW_REG3 0x0268 ++#define IOMMU_PMU_HIT_HIGH_REG3 0x026C ++#define IOMMU_PMU_ACCESS_LOW_REG4 0x0270 ++#define IOMMU_PMU_ACCESS_HIGH_REG4 0x0274 ++#define IOMMU_PMU_HIT_LOW_REG4 0x0278 ++#define IOMMU_PMU_HIT_HIGH_REG4 0x027C ++#define IOMMU_PMU_ACCESS_LOW_REG5 0x0280 ++#define IOMMU_PMU_ACCESS_HIGH_REG5 0x0284 ++#define IOMMU_PMU_HIT_LOW_REG5 0x0288 ++#define IOMMU_PMU_HIT_HIGH_REG5 0x028C ++ ++#define IOMMU_PMU_ACCESS_LOW_REG6 0x0290 ++#define IOMMU_PMU_ACCESS_HIGH_REG6 0x0294 ++#define IOMMU_PMU_HIT_LOW_REG6 0x0298 ++#define IOMMU_PMU_HIT_HIGH_REG6 0x029C ++#define IOMMU_PMU_ACCESS_LOW_REG7 0x02D0 ++#define IOMMU_PMU_ACCESS_HIGH_REG7 0x02D4 ++#define IOMMU_PMU_HIT_LOW_REG7 0x02D8 ++#define IOMMU_PMU_HIT_HIGH_REG7 0x02DC ++#define IOMMU_PMU_ACCESS_LOW_REG8 0x02E0 ++#define IOMMU_PMU_ACCESS_HIGH_REG8 0x02E4 ++#define IOMMU_PMU_HIT_LOW_REG8 0x02E8 ++#define IOMMU_PMU_HIT_HIGH_REG8 0x02EC ++ ++#define IOMMU_PMU_TL_LOW_REG0 0x0300 ++#define IOMMU_PMU_TL_HIGH_REG0 0x0304 ++#define IOMMU_PMU_ML_REG0 0x0308 ++ ++#define IOMMU_PMU_TL_LOW_REG1 0x0310 ++#define IOMMU_PMU_TL_HIGH_REG1 0x0314 ++#define IOMMU_PMU_ML_REG1 0x0318 ++ ++#define IOMMU_PMU_TL_LOW_REG2 0x0320 ++#define IOMMU_PMU_TL_HIGH_REG2 0x0324 ++#define IOMMU_PMU_ML_REG2 0x0328 ++ ++#define IOMMU_PMU_TL_LOW_REG3 0x0330 ++#define IOMMU_PMU_TL_HIGH_REG3 0x0334 ++#define IOMMU_PMU_ML_REG3 0x0338 ++ ++#define IOMMU_PMU_TL_LOW_REG4 0x0340 ++#define IOMMU_PMU_TL_HIGH_REG4 0x0344 ++#define IOMMU_PMU_ML_REG4 0x0348 ++ ++#define IOMMU_PMU_TL_LOW_REG5 0x0350 ++#define IOMMU_PMU_TL_HIGH_REG5 0x0354 ++#define IOMMU_PMU_ML_REG5 0x0358 ++ ++#define IOMMU_PMU_TL_LOW_REG6 0x0360 ++#define IOMMU_PMU_TL_HIGH_REG6 0x0364 ++#define IOMMU_PMU_ML_REG6 0x0368 ++ ++#define IOMMU_RESET_SHIFT 31 ++#define IOMMU_RESET_MASK (1 << IOMMU_RESET_SHIFT) ++#define IOMMU_RESET_SET (0 << 31) ++#define IOMMU_RESET_RELEASE (1 << 31) ++ ++/* ++ * IOMMU enable register field ++ */ ++#define IOMMU_ENABLE 0x1 ++ ++/* ++ * IOMMU interrupt id mask ++ */ ++#define MICRO_TLB0_INVALID_INTER_MASK 0x1 ++#define MICRO_TLB1_INVALID_INTER_MASK 0x2 ++#define MICRO_TLB2_INVALID_INTER_MASK 0x4 ++#define MICRO_TLB3_INVALID_INTER_MASK 0x8 ++#define MICRO_TLB4_INVALID_INTER_MASK 0x10 ++#define MICRO_TLB5_INVALID_INTER_MASK 0x20 ++#define MICRO_TLB6_INVALID_INTER_MASK 0x40 ++ ++#define L1_PAGETABLE_INVALID_INTER_MASK 0x10000 ++#define L2_PAGETABLE_INVALID_INTER_MASK 0x20000 ++ ++/** ++ * sun8iw15p1 ++ * DE : masterID 0 ++ * E_EDMA: masterID 1 ++ * E_FE: masterID 2 ++ * VE: masterID 3 ++ * CSI: masterID 4 ++ * G2D: masterID 5 ++ * E_BE: masterID 6 ++ * ++ * sun50iw9p1: ++ * DE : masterID 0 ++ * DI: masterID 1 ++ * VE_R: masterID 2 ++ * VE: masterID 3 ++ * CSI0: masterID 4 ++ * CSI1: masterID 5 ++ * G2D: masterID 6 ++ * sun8iw19p1: ++ * DE :>--->-------masterID 0 ++ * EISE: masterID 1 ++ * AI: masterID 2 ++ * VE:>---->-------masterID 3 ++ * CSI: >-->----masterID 4 ++ * ISP:>-->------ masterID 5 ++ * G2D:>--->-------masterID 6 ++ * sun8iw21: ++ * VE : masterID 0 ++ * CSI: masterID 1 ++ * DE: masterID 2 ++ * G2D: masterID 3 ++ * ISP: masterID 4 ++ * RISCV: masterID 5 ++ * NPU: masterID 6 ++ */ ++#define DEFAULT_BYPASS_VALUE 0x7f ++static const u32 master_id_bitmap[] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40}; ++ ++#define sunxi_wait_when(COND, MS) ({ \ ++ unsigned long timeout__ = jiffies + msecs_to_jiffies(MS) + 1; \ ++ int ret__ = 0; \ ++ while ((COND)) { \ ++ if (time_after(jiffies, timeout__)) { \ ++ ret__ = (!COND) ? 0 : -ETIMEDOUT; \ ++ break; \ ++ } \ ++ udelay(1); \ ++ } \ ++ ret__; \ ++}) ++ ++/* ++ * The format of device tree, and client device how to use it. ++ * ++ * /{ ++ * .... ++ * smmu: iommu@xxxxx { ++ * compatible = "allwinner,iommu"; ++ * reg = ; ++ * interrupts = ; ++ * interrupt-names = "iommu-irq"; ++ * clocks = <&iommu_clk>; ++ * clock-name = "iommu-clk"; ++ * #iommu-cells = <1>; ++ * status = "enabled"; ++ * }; ++ * ++ * de@xxxxx { ++ * ..... ++ * iommus = <&smmu ID>; ++ * }; ++ * ++ * } ++ * ++ * Here, ID number is 0 ~ 5, every client device have a unique id. ++ * Every id represent a micro TLB, also represent a master device. ++ * ++ */ ++ ++enum sunxi_iommu_version { ++ IOMMU_VERSION_V10 = 0x10, ++ IOMMU_VERSION_V11, ++ IOMMU_VERSION_V12, ++ IOMMU_VERSION_V13, ++ IOMMU_VERSION_V14, ++}; ++ ++struct sunxi_iommu_plat_data { ++ u32 version; ++ u32 tlb_prefetch; ++ u32 tlb_invalid_mode; ++ u32 ptw_invalid_mode; ++ const char *master[8]; ++}; ++ ++struct sunxi_iommu_dev { ++ struct iommu_device iommu; ++ struct device *dev; ++ void __iomem *base; ++ struct clk *clk; ++ int irq; ++ u32 bypass; ++ spinlock_t iommu_lock; ++ struct list_head rsv_list; ++ const struct sunxi_iommu_plat_data *plat_data; ++ struct iommu_domain *identity_domain; ++ struct sunxi_iommu_domain *debug_domain; ++}; ++ ++struct sunxi_iommu_domain { ++ unsigned int *pgtable; /* first page directory, size is 16KB */ ++ u32 *sg_buffer; ++ struct spinlock dt_lock; /* lock for modifying page table @ pgtable */ ++ struct dma_iommu_mapping *mapping; ++ struct iommu_domain domain; ++ /* struct iova_domain iovad; */ ++ /* list of master device, it represent a micro TLB */ ++ struct list_head mdevs; ++ spinlock_t lock; ++}; ++ ++/* ++ * sunxi master device which use iommu. ++ */ ++struct sunxi_mdev { ++ struct list_head node; /* for sunxi_iommu mdevs list */ ++ struct device *dev; /* the master device */ ++ unsigned int tlbid; /* micro TLB id, distinguish device by it */ ++ bool flag; ++}; ++ ++struct sunxi_iommu_owner { ++ unsigned int tlbid; ++ bool flag; ++ struct sunxi_iommu_dev *data; ++ struct device *dev; ++ struct dma_iommu_mapping *mapping; ++}; ++ ++#define _max(x, y) (((u64)(x) > (u64)(y)) ? (x) : (y)) ++ ++static struct kmem_cache *iopte_cache; ++static struct sunxi_iommu_dev *global_iommu_dev; ++static bool iommu_hw_init_flag; ++static struct device *dma_dev; ++ ++static sunxi_iommu_fault_cb sunxi_iommu_fault_notify_cbs[7]; ++u32 sunxi_iommu_dump_rsv_list(struct list_head *rsv_list, ssize_t len, ++ char *buf, size_t buf_len, bool for_sysfs_show); ++int sunxi_iommu_check_cmd(struct device *dev, void *data); ++ ++void sun55i_iommu_register_fault_cb(sunxi_iommu_fault_cb cb, unsigned int master_id) ++{ ++ if (master_id >= ARRAY_SIZE(sunxi_iommu_fault_notify_cbs)) ++ return; ++ sunxi_iommu_fault_notify_cbs[master_id] = cb; ++} ++EXPORT_SYMBOL_GPL(sun55i_iommu_register_fault_cb); ++ ++static inline u32 sunxi_iommu_read(struct sunxi_iommu_dev *iommu, ++ u32 offset) ++{ ++ return readl(iommu->base + offset); ++} ++ ++static inline void sunxi_iommu_write(struct sunxi_iommu_dev *iommu, ++ u32 offset, u32 value) ++{ ++ writel(value, iommu->base + offset); ++} ++ ++void sun55i_reset_device_iommu(unsigned int master_id) ++{ ++ unsigned int regval; ++ struct sunxi_iommu_dev *iommu = global_iommu_dev; ++ ++ if (master_id >= 7) ++ return; ++ ++ if (!iommu) ++ return; ++ ++ regval = sunxi_iommu_read(iommu, IOMMU_RESET_REG); ++ sunxi_iommu_write(iommu, IOMMU_RESET_REG, regval & (~(1 << master_id))); ++ regval = sunxi_iommu_read(iommu, IOMMU_RESET_REG); ++ if (!(regval & ((1 << master_id)))) { ++ sunxi_iommu_write(iommu, IOMMU_RESET_REG, regval | ((1 << master_id))); ++ } ++} ++EXPORT_SYMBOL(sun55i_reset_device_iommu); ++ ++void sun55i_enable_device_iommu(struct sunxi_iommu_dev *iommu, unsigned int master_id, bool flag) ++{ ++ unsigned long mflag; ++ ++ if (!iommu) ++ return; ++ ++ if (master_id >= ARRAY_SIZE(master_id_bitmap)) ++ return; ++ ++ spin_lock_irqsave(&iommu->iommu_lock, mflag); ++ if (flag) ++ iommu->bypass &= ~(master_id_bitmap[master_id]); ++ else ++ iommu->bypass |= master_id_bitmap[master_id]; ++ sunxi_iommu_write(iommu, IOMMU_BYPASS_REG, iommu->bypass); ++ spin_unlock_irqrestore(&iommu->iommu_lock, mflag); ++} ++EXPORT_SYMBOL(sun55i_enable_device_iommu); ++ ++static int sun55i_tlb_flush(struct sunxi_iommu_dev *iommu) ++{ ++ int ret; ++ ++ /* enable the maximum number(7) of master to fit all platform */ ++ sunxi_iommu_write(iommu, IOMMU_TLB_FLUSH_ENABLE_REG, 0x0003007f); ++ ret = sunxi_wait_when( ++ (sunxi_iommu_read(iommu, IOMMU_TLB_FLUSH_ENABLE_REG)), 2); ++ if (ret) ++ dev_err(iommu->dev, "Enable flush all request timed out\n"); ++ ++ return ret; ++} ++ ++static int sun55i_iommu_hw_init(struct sunxi_iommu_dev *iommu, struct sunxi_iommu_domain *sunxi_domain) ++{ ++ int ret = 0; ++ int iommu_enable = 0; ++ unsigned long mflag; ++ const struct sunxi_iommu_plat_data *plat_data = iommu->plat_data; ++ ++ spin_lock_irqsave(&iommu->iommu_lock, mflag); ++ ++ if (sunxi_domain) { ++ phys_addr_t dte_addr = __pa(sunxi_domain->pgtable); ++ sunxi_iommu_write(iommu, IOMMU_TTB_REG, dte_addr); ++ } ++ ++ /* ++ * set preftech functions, including: ++ * master prefetching and only prefetch valid page to TLB/PTW ++ */ ++ sunxi_iommu_write(iommu, IOMMU_TLB_PREFETCH_REG, plat_data->tlb_prefetch); ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_MODE_SEL_REG, plat_data->tlb_invalid_mode); ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_MODE_SEL_REG, plat_data->ptw_invalid_mode); ++ ++ /* disable interrupt of prefetch */ ++ sunxi_iommu_write(iommu, IOMMU_INT_ENABLE_REG, 0x3003f); ++ sunxi_iommu_write(iommu, IOMMU_BYPASS_REG, iommu->bypass); ++ ++ ret = sun55i_tlb_flush(iommu); ++ if (ret) { ++ dev_err(iommu->dev, "Enable flush all request timed out\n"); ++ goto out; ++ } ++ sunxi_iommu_write(iommu, IOMMU_AUTO_GATING_REG, 0x1); ++ sunxi_iommu_write(iommu, IOMMU_ENABLE_REG, IOMMU_ENABLE); ++ iommu_enable = sunxi_iommu_read(iommu, IOMMU_ENABLE_REG); ++ if (iommu_enable != 0x1) { ++ iommu_enable = sunxi_iommu_read(iommu, IOMMU_ENABLE_REG); ++ if (iommu_enable != 0x1) { ++ dev_err(iommu->dev, "iommu enable failed! No iommu in bitfile!\n"); ++ ret = -ENODEV; ++ goto out; ++ } ++ } ++ iommu_hw_init_flag = true; ++ ++out: ++ spin_unlock_irqrestore(&iommu->iommu_lock, mflag); ++ ++ return ret; ++} ++ ++static int sun55i_tlb_invalid(dma_addr_t iova, dma_addr_t iova_mask) ++{ ++ struct sunxi_iommu_dev *iommu = global_iommu_dev; ++ dma_addr_t iova_end = iova_mask; ++ int ret = 0; ++ unsigned long mflag; ++ ++ spin_lock_irqsave(&iommu->iommu_lock, mflag); ++ /* new TLB invalid function: use range(start, end) to invalid TLB page */ ++ pr_debug("iommu: TLB invalid:0x%x-0x%x\n", (unsigned int)iova, ++ (unsigned int)iova_end); ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_START_ADDR_REG, iova); ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_END_ADDR_REG, iova_end); ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_ENABLE_REG, 0x1); ++ ++ ret = sunxi_wait_when( ++ (sunxi_iommu_read(iommu, IOMMU_TLB_IVLD_ENABLE_REG)&0x1), 2); ++ if (ret) { ++ dev_err(iommu->dev, "TLB cache invalid timed out\n"); ++ } ++ spin_unlock_irqrestore(&iommu->iommu_lock, mflag); ++ ++ return ret; ++} ++ ++static int sun55i_ptw_cache_invalid(dma_addr_t iova_start, dma_addr_t iova_end) ++{ ++ struct sunxi_iommu_dev *iommu = global_iommu_dev; ++ int ret = 0; ++ unsigned long mflag; ++ ++ spin_lock_irqsave(&iommu->iommu_lock, mflag); ++ /* new PTW invalid function: use range(start, end) to invalid PTW page */ ++ pr_debug("iommu: PTW invalid:0x%x-0x%x\n", (unsigned int)iova_start, ++ (unsigned int)iova_end); ++ WARN_ON(iova_end == 0); ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_START_ADDR_REG, iova_start); ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_END_ADDR_REG, iova_end); ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_ENABLE_REG, 0x1); ++ ++ ret = sunxi_wait_when( ++ (sunxi_iommu_read(iommu, IOMMU_PC_IVLD_ENABLE_REG)&0x1), 2); ++ if (ret) { ++ dev_err(iommu->dev, "PTW cache invalid timed out\n"); ++ goto out; ++ } ++ ++out: ++ spin_unlock_irqrestore(&iommu->iommu_lock, mflag); ++ ++ return ret; ++} ++ ++static void sun55i_zap_tlb(unsigned long iova, size_t size) ++{ ++ sun55i_tlb_invalid(iova, iova + 2 * SPAGE_SIZE); ++ sun55i_tlb_invalid(iova + size - SPAGE_SIZE, iova + size + 8 * SPAGE_SIZE); ++ sun55i_ptw_cache_invalid(iova, iova + SPD_SIZE); ++ sun55i_ptw_cache_invalid(iova + size - SPD_SIZE, iova + size); ++ ++ return; ++} ++ ++static int sun55i_iommu_map(struct iommu_domain *domain, unsigned long iova, ++ phys_addr_t paddr, size_t size, size_t count, int prot, ++ gfp_t gfp, size_t *mapped) ++{ ++ struct sunxi_iommu_domain *sunxi_domain; ++ size_t iova_start, iova_end; ++ unsigned long total_size = size * count; ++ int ret; ++ unsigned long flags; ++ ++ sunxi_domain = container_of(domain, struct sunxi_iommu_domain, domain); ++ WARN_ON(sunxi_domain->pgtable == NULL); ++ ++ iova_start = iova & IOMMU_PT_MASK; ++ iova_end = SPAGE_ALIGN(iova + total_size); ++ ++ spin_lock_irqsave(&sunxi_domain->dt_lock, flags); ++ ++ ret = sunxi_pgtable_prepare_l1_tables(sunxi_domain->pgtable, iova_start, ++ iova_end, prot); ++ if (ret) { ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ if (mapped) ++ *mapped = 0; ++ return -ENOMEM; ++ } ++ ++ sunxi_pgtable_prepare_l2_tables(sunxi_domain->pgtable, ++ iova_start, iova_end, paddr, prot); ++ ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ ++ if (mapped) ++ *mapped = total_size; ++ ++ return 0; ++} ++ ++static size_t sun55i_iommu_unmap(struct iommu_domain *domain, unsigned long iova, ++ size_t size, size_t count, ++ struct iommu_iotlb_gather *gather) ++{ ++ struct sunxi_iommu_domain *sunxi_domain; ++ const struct sunxi_iommu_plat_data *plat_data; ++ size_t iova_start, iova_end; ++ unsigned long total_size = size * count; ++ int iova_tail_size; ++ unsigned long flags; ++ ++ sunxi_domain = container_of(domain, struct sunxi_iommu_domain, domain); ++ plat_data = global_iommu_dev->plat_data; ++ WARN_ON(sunxi_domain->pgtable == NULL); ++ ++ iova_start = iova & IOMMU_PT_MASK; ++ iova_end = SPAGE_ALIGN(iova + total_size); ++ ++ if (gather) { ++ if (gather->start > iova_start) ++ gather->start = iova_start; ++ if (gather->end < iova_end) ++ gather->end = iova_end; ++ } ++ ++ spin_lock_irqsave(&sunxi_domain->dt_lock, flags); ++ ++ sun55i_tlb_invalid(iova_start, iova_end); ++ sun55i_ptw_cache_invalid(iova_start, iova_end); ++ ++ for (; iova_start < iova_end; ) { ++ iova_tail_size = sunxi_pgtable_delete_l2_tables( ++ sunxi_domain->pgtable, iova_start, iova_end); ++ if (iova_tail_size < 0) { ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ return 0; ++ } ++ if (iova_tail_size == 0) ++ break; ++ ++ sun55i_ptw_cache_invalid(iova_start, iova_start + iova_tail_size); ++ iova_start += iova_tail_size; ++ } ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ ++ return total_size; ++} ++ ++static int sun55i_iommu_iotlb_sync_map(struct iommu_domain *domain, ++ unsigned long iova, size_t size) ++{ ++ struct sunxi_iommu_domain *sunxi_domain = ++ container_of(domain, struct sunxi_iommu_domain, domain); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&sunxi_domain->dt_lock, flags); ++ sun55i_zap_tlb(iova, size); ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ ++ return 0; ++} ++ ++static phys_addr_t sun55i_iommu_iova_to_phys(struct iommu_domain *domain, ++ dma_addr_t iova) ++{ ++ struct sunxi_iommu_domain *sunxi_domain = ++ container_of(domain, struct sunxi_iommu_domain, domain); ++ phys_addr_t ret = 0; ++ unsigned long flags; ++ ++ ++ WARN_ON(sunxi_domain->pgtable == NULL); ++ spin_lock_irqsave(&sunxi_domain->dt_lock, flags); ++ ret = sunxi_pgtable_iova_to_phys(sunxi_domain->pgtable, iova); ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ ++ return ret; ++} ++ ++static struct iommu_domain *sun55i_iommu_domain_alloc_paging(struct device *dev) ++{ ++ struct sunxi_iommu_domain *sunxi_domain; ++ ++ sunxi_domain = kzalloc(sizeof(*sunxi_domain), GFP_KERNEL); ++ if (!sunxi_domain) ++ return NULL; ++ ++ sunxi_domain->pgtable = sunxi_pgtable_alloc(); ++ if (!sunxi_domain->pgtable) { ++ pr_err("sunxi domain get pgtable failed\n"); ++ goto err_page; ++ } ++ ++ sunxi_domain->sg_buffer = (unsigned int *)__get_free_pages( ++ GFP_KERNEL, get_order(MAX_SG_TABLE_SIZE)); ++ if (!sunxi_domain->sg_buffer) { ++ pr_err("sunxi domain get sg_buffer failed\n"); ++ goto err_sg_buffer; ++ } ++ ++ sunxi_domain->domain.geometry.aperture_start = 0; ++ sunxi_domain->domain.geometry.aperture_end = (1ULL << 32) - 1; ++ sunxi_domain->domain.geometry.force_aperture = true; ++ spin_lock_init(&sunxi_domain->dt_lock); ++ ++ if (global_iommu_dev) ++ global_iommu_dev->debug_domain = sunxi_domain; ++ ++ if (!iommu_hw_init_flag) { ++ if (sun55i_iommu_hw_init(global_iommu_dev, sunxi_domain)) ++ pr_err("sunxi iommu hardware init failed\n"); ++ } ++ ++ return &sunxi_domain->domain; ++ ++err_sg_buffer: ++ sunxi_pgtable_free(sunxi_domain->pgtable); ++ sunxi_domain->pgtable = NULL; ++err_page: ++ kfree(sunxi_domain); ++ ++ return NULL; ++} ++ ++static void sun55i_iommu_domain_free(struct iommu_domain *domain) ++{ ++ struct sunxi_iommu_domain *sunxi_domain = ++ container_of(domain, struct sunxi_iommu_domain, domain); ++ unsigned long flags; ++ ++ spin_lock_irqsave(&sunxi_domain->dt_lock, flags); ++ sunxi_pgtable_clear(sunxi_domain->pgtable); ++ sun55i_tlb_flush(global_iommu_dev); ++ spin_unlock_irqrestore(&sunxi_domain->dt_lock, flags); ++ sunxi_pgtable_free(sunxi_domain->pgtable); ++ sunxi_domain->pgtable = NULL; ++ free_pages((unsigned long)sunxi_domain->sg_buffer, ++ get_order(MAX_SG_TABLE_SIZE)); ++ sunxi_domain->sg_buffer = NULL; ++ kfree(sunxi_domain); ++} ++ ++static int sun55i_iommu_attach_dev(struct iommu_domain *domain, ++ struct device *dev, ++ struct iommu_domain *old_domain) ++{ ++ return 0; ++} ++ ++static void sun55i_iommu_probe_device_finalize(struct device *dev) ++{ ++ struct sunxi_iommu_owner *owner = dev_iommu_priv_get(dev); ++ ++ WARN(!dev->dma_mask || *dev->dma_mask == 0, "NULL or 0 dma mask will fail iommu setup\n"); ++ ++ sun55i_enable_device_iommu(owner->data, owner->tlbid, owner->flag); ++} ++ ++static struct iommu_device *sun55i_iommu_probe_device(struct device *dev) ++{ ++ struct sunxi_iommu_owner *owner = dev_iommu_priv_get(dev); ++ ++ if (!owner) /* Not a iommu client device */ ++ return ERR_PTR(-ENODEV); ++ ++ return &owner->data->iommu; ++} ++ ++static void sun55i_iommu_release_device(struct device *dev) ++{ ++ struct sunxi_iommu_owner *owner = dev_iommu_priv_get(dev); ++ ++ if (!owner) ++ return; ++ ++ sun55i_enable_device_iommu(owner->data, owner->tlbid, false); ++ dev->iommu_group = NULL; ++ devm_kfree(dev, dev->dma_parms); ++ dev->dma_parms = NULL; ++ kfree(owner); ++ owner = NULL; ++ dev_iommu_priv_set(dev, NULL); ++} ++ ++static int sun55i_iommu_of_xlate(struct device *dev, ++ const struct of_phandle_args *args) ++{ ++ struct sunxi_iommu_owner *owner = dev_iommu_priv_get(dev); ++ struct platform_device *sysmmu = of_find_device_by_node(args->np); ++ struct sunxi_iommu_dev *data; ++ ++ if (!sysmmu) ++ return -ENODEV; ++ ++ data = platform_get_drvdata(sysmmu); ++ if (data == NULL) ++ return -ENODEV; ++ ++ if (!owner) { ++ owner = kzalloc(sizeof(*owner), GFP_KERNEL); ++ if (!owner) ++ return -ENOMEM; ++ owner->tlbid = args->args[0]; ++ if (args->args_count > 1) ++ owner->flag = args->args[1]; ++ else ++ owner->flag = 0; ++ owner->data = data; ++ owner->dev = dev; ++ dev_iommu_priv_set(dev, owner); ++ } ++ ++ return 0; ++} ++ ++static irqreturn_t sunxi_iommu_irq(int irq, void *dev_id) ++{ ++ ++ u32 inter_status_reg = 0; ++ u32 addr_reg = 0; ++ u32 int_masterid_bitmap = 0; ++ u32 data_reg = 0; ++ u32 l1_pgint_reg = 0; ++ u32 l2_pgint_reg = 0; ++ u32 master_id = 0; ++ unsigned long mflag; ++ struct sunxi_iommu_dev *iommu = dev_id; ++ const struct sunxi_iommu_plat_data *plat_data = iommu->plat_data; ++ ++ spin_lock_irqsave(&iommu->iommu_lock, mflag); ++ inter_status_reg = sunxi_iommu_read(iommu, IOMMU_INT_STA_REG) & 0x3ffff; ++ l1_pgint_reg = sunxi_iommu_read(iommu, IOMMU_L1PG_INT_REG); ++ l2_pgint_reg = sunxi_iommu_read(iommu, IOMMU_L2PG_INT_REG); ++ int_masterid_bitmap = inter_status_reg | l1_pgint_reg | l2_pgint_reg; ++ ++ if (inter_status_reg & MICRO_TLB0_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[0]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG0); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG0); ++ } else if (inter_status_reg & MICRO_TLB1_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[1]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG1); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG1); ++ } else if (inter_status_reg & MICRO_TLB2_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[2]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG2); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG2); ++ } else if (inter_status_reg & MICRO_TLB3_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[3]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG3); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG3); ++ } else if (inter_status_reg & MICRO_TLB4_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[4]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG4); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG4); ++ } else if (inter_status_reg & MICRO_TLB5_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[5]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG5); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG5); ++ } else if (inter_status_reg & MICRO_TLB6_INVALID_INTER_MASK) { ++ pr_err("%s Invalid Authority\n", plat_data->master[6]); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG6); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG6); ++ } else if (inter_status_reg & L1_PAGETABLE_INVALID_INTER_MASK) { ++ /* It's OK to prefetch an invalid page, no need to print msg for debug. */ ++ if (!(int_masterid_bitmap & (1U << 31))) ++ pr_err("L1 PageTable Invalid\n"); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG7); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG7); ++ } else if (inter_status_reg & L2_PAGETABLE_INVALID_INTER_MASK) { ++ if (!(int_masterid_bitmap & (1U << 31))) ++ pr_err("L2 PageTable Invalid\n"); ++ addr_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG8); ++ data_reg = sunxi_iommu_read(iommu, IOMMU_INT_ERR_DATA_REG8); ++ } else ++ pr_err("sunxi iommu int error!!!\n"); ++ ++ if (!(int_masterid_bitmap & (1U << 31))) { ++ int_masterid_bitmap &= 0xffff; ++ ++ if (int_masterid_bitmap) { ++ master_id = __ffs(int_masterid_bitmap); ++ pr_err("Bug is in %s module, invalid address: 0x%x, data:0x%x, id:0x%x\n", ++ plat_data->master[master_id], addr_reg, data_reg, ++ int_masterid_bitmap); ++ ++ if (sunxi_iommu_fault_notify_cbs[master_id]) ++ sunxi_iommu_fault_notify_cbs[master_id](); ++ } else { ++ pr_err("Bug in unknown module (id=0), invalid address: 0x%x, data:0x%x\n", ++ addr_reg, data_reg); ++ } ++ } ++ ++ /* invalid TLB */ ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_START_ADDR_REG, addr_reg); ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_END_ADDR_REG, addr_reg + 4 * SPAGE_SIZE); ++ sunxi_iommu_write(iommu, IOMMU_TLB_IVLD_ENABLE_REG, 0x1); ++ while (sunxi_iommu_read(iommu, IOMMU_TLB_IVLD_ENABLE_REG) & 0x1) ++ ; ++ ++ /* invalid PTW */ ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_START_ADDR_REG, addr_reg); ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_END_ADDR_REG, addr_reg + 2 * SPD_SIZE); ++ sunxi_iommu_write(iommu, IOMMU_PC_IVLD_ENABLE_REG, 0x1); ++ while (sunxi_iommu_read(iommu, IOMMU_PC_IVLD_ENABLE_REG) & 0x1) ++ ; ++ ++ sunxi_iommu_write(iommu, IOMMU_INT_CLR_REG, inter_status_reg); ++ inter_status_reg |= (l1_pgint_reg | l2_pgint_reg); ++ inter_status_reg &= 0xffff; ++ sunxi_iommu_write(iommu, IOMMU_RESET_REG, ~inter_status_reg); ++ sunxi_iommu_write(iommu, IOMMU_RESET_REG, 0xffffffff); ++ spin_unlock_irqrestore(&iommu->iommu_lock, mflag); ++ ++ return IRQ_HANDLED; ++} ++ ++static ssize_t sunxi_iommu_enable_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct sunxi_iommu_dev *iommu = global_iommu_dev; ++ u32 data; ++ ++ spin_lock(&iommu->iommu_lock); ++ data = sunxi_iommu_read(iommu, IOMMU_PMU_ENABLE_REG); ++ spin_unlock(&iommu->iommu_lock); ++ ++ return scnprintf(buf, PAGE_SIZE, ++ "enable = %d\n", data & 0x1 ? 1 : 0); ++} ++ ++static ssize_t sunxi_iommu_enable_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct sunxi_iommu_dev *iommu = global_iommu_dev; ++ unsigned long val; ++ u32 data; ++ int retval; ++ ++ if (kstrtoul(buf, 0, &val)) ++ return -EINVAL; ++ ++ if (val) { ++ spin_lock(&iommu->iommu_lock); ++ data = sunxi_iommu_read(iommu, IOMMU_PMU_ENABLE_REG); ++ sunxi_iommu_write(iommu, IOMMU_PMU_ENABLE_REG, data | 0x1); ++ data = sunxi_iommu_read(iommu, IOMMU_PMU_CLR_REG); ++ sunxi_iommu_write(iommu, IOMMU_PMU_CLR_REG, data | 0x1); ++ retval = sunxi_wait_when((sunxi_iommu_read(iommu, ++ IOMMU_PMU_CLR_REG) & 0x1), 1); ++ if (retval) ++ dev_err(iommu->dev, "Clear PMU Count timed out\n"); ++ spin_unlock(&iommu->iommu_lock); ++ } else { ++ spin_lock(&iommu->iommu_lock); ++ data = sunxi_iommu_read(iommu, IOMMU_PMU_CLR_REG); ++ sunxi_iommu_write(iommu, IOMMU_PMU_CLR_REG, data | 0x1); ++ retval = sunxi_wait_when((sunxi_iommu_read(iommu, ++ IOMMU_PMU_CLR_REG) & 0x1), 1); ++ if (retval) ++ dev_err(iommu->dev, "Clear PMU Count timed out\n"); ++ data = sunxi_iommu_read(iommu, IOMMU_PMU_ENABLE_REG); ++ sunxi_iommu_write(iommu, IOMMU_PMU_ENABLE_REG, data & ~0x1); ++ spin_unlock(&iommu->iommu_lock); ++ } ++ ++ return count; ++} ++ ++static ssize_t sunxi_iommu_profilling_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct sunxi_iommu_dev *iommu = global_iommu_dev; ++ const struct sunxi_iommu_plat_data *plat_data = iommu->plat_data; ++ struct { ++ u64 macrotlb_access_count; ++ u64 macrotlb_hit_count; ++ u64 ptwcache_access_count; ++ u64 ptwcache_hit_count; ++ struct { ++ u64 access_count; ++ u64 hit_count; ++ u64 latency; ++ u32 max_latency; ++ } micro_tlb[7]; ++ } *iommu_profile; ++ iommu_profile = kmalloc(sizeof(*iommu_profile), GFP_KERNEL); ++ if (!iommu_profile) ++ return 0; ++ int len; ++ spin_lock(&iommu->iommu_lock); ++ ++ iommu_profile->micro_tlb[0].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG0) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG0); ++ iommu_profile->micro_tlb[0].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG0) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG0); ++ ++ iommu_profile->micro_tlb[1].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG1) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG1); ++ iommu_profile->micro_tlb[1].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG1) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG1); ++ ++ iommu_profile->micro_tlb[2].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG2) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG2); ++ iommu_profile->micro_tlb[2].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG2) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG2); ++ ++ iommu_profile->micro_tlb[3].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG3) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG3); ++ iommu_profile->micro_tlb[3].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG3) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG3); ++ ++ iommu_profile->micro_tlb[4].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG4) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG4); ++ iommu_profile->micro_tlb[4].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG4) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG4); ++ ++ iommu_profile->micro_tlb[5].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG5) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG5); ++ iommu_profile->micro_tlb[5].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG5) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG5); ++ ++ iommu_profile->micro_tlb[6].access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG6) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG6); ++ iommu_profile->micro_tlb[6].hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG6) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG6); ++ ++ iommu_profile->macrotlb_access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG7) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG7); ++ iommu_profile->macrotlb_hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG7) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG7); ++ ++ iommu_profile->ptwcache_access_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_HIGH_REG8) & ++ 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_ACCESS_LOW_REG8); ++ iommu_profile->ptwcache_hit_count = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_HIT_HIGH_REG8) & 0x7ff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_HIT_LOW_REG8); ++ ++ iommu_profile->micro_tlb[0].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG0) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG0); ++ iommu_profile->micro_tlb[1].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG1) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG1); ++ iommu_profile->micro_tlb[2].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG2) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG2); ++ iommu_profile->micro_tlb[3].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG3) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG3); ++ iommu_profile->micro_tlb[4].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG4) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG4); ++ iommu_profile->micro_tlb[5].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG5) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG5); ++ ++ iommu_profile->micro_tlb[6].latency = ++ ((u64)(sunxi_iommu_read(iommu, IOMMU_PMU_TL_HIGH_REG6) & ++ 0x3ffff) ++ << 32) | ++ sunxi_iommu_read(iommu, IOMMU_PMU_TL_LOW_REG6); ++ ++ iommu_profile->micro_tlb[0].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG0); ++ iommu_profile->micro_tlb[1].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG1); ++ iommu_profile->micro_tlb[2].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG2); ++ iommu_profile->micro_tlb[3].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG3); ++ iommu_profile->micro_tlb[4].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG4); ++ iommu_profile->micro_tlb[5].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG5); ++ iommu_profile->micro_tlb[6].max_latency = ++ sunxi_iommu_read(iommu, IOMMU_PMU_ML_REG6); ++ ++ spin_unlock(&iommu->iommu_lock); ++ ++ len = scnprintf( ++ buf, PAGE_SIZE, ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "%s_access_count = 0x%llx\n" ++ "%s_hit_count = 0x%llx\n" ++ "macrotlb_access_count = 0x%llx\n" ++ "macrotlb_hit_count = 0x%llx\n" ++ "ptwcache_access_count = 0x%llx\n" ++ "ptwcache_hit_count = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_total_latency = 0x%llx\n" ++ "%s_max_latency = 0x%x\n" ++ "%s_max_latency = 0x%x\n" ++ "%s_max_latency = 0x%x\n" ++ "%s_max_latency = 0x%x\n" ++ "%s_max_latency = 0x%x\n" ++ "%s_max_latency = 0x%x\n" ++ "%s_max_latency = 0x%x\n", ++ plat_data->master[0], iommu_profile->micro_tlb[0].access_count, ++ plat_data->master[0], iommu_profile->micro_tlb[0].hit_count, ++ plat_data->master[1], iommu_profile->micro_tlb[1].access_count, ++ plat_data->master[1], iommu_profile->micro_tlb[1].hit_count, ++ plat_data->master[2], iommu_profile->micro_tlb[2].access_count, ++ plat_data->master[2], iommu_profile->micro_tlb[2].hit_count, ++ plat_data->master[3], iommu_profile->micro_tlb[3].access_count, ++ plat_data->master[3], iommu_profile->micro_tlb[3].hit_count, ++ plat_data->master[4], iommu_profile->micro_tlb[4].access_count, ++ plat_data->master[4], iommu_profile->micro_tlb[4].hit_count, ++ plat_data->master[5], iommu_profile->micro_tlb[5].access_count, ++ plat_data->master[5], iommu_profile->micro_tlb[5].hit_count, ++ plat_data->master[6], iommu_profile->micro_tlb[6].access_count, ++ plat_data->master[6], iommu_profile->micro_tlb[6].hit_count, ++ iommu_profile->macrotlb_access_count, ++ iommu_profile->macrotlb_hit_count, ++ iommu_profile->ptwcache_access_count, ++ iommu_profile->ptwcache_hit_count, plat_data->master[0], ++ iommu_profile->micro_tlb[0].latency, plat_data->master[1], ++ iommu_profile->micro_tlb[1].latency, plat_data->master[2], ++ iommu_profile->micro_tlb[2].latency, plat_data->master[3], ++ iommu_profile->micro_tlb[3].latency, plat_data->master[4], ++ iommu_profile->micro_tlb[4].latency, plat_data->master[5], ++ iommu_profile->micro_tlb[5].latency, plat_data->master[6], ++ iommu_profile->micro_tlb[6].latency, plat_data->master[0], ++ iommu_profile->micro_tlb[0].max_latency, plat_data->master[1], ++ iommu_profile->micro_tlb[1].max_latency, plat_data->master[2], ++ iommu_profile->micro_tlb[2].max_latency, plat_data->master[3], ++ iommu_profile->micro_tlb[3].max_latency, plat_data->master[4], ++ iommu_profile->micro_tlb[4].max_latency, plat_data->master[5], ++ iommu_profile->micro_tlb[5].max_latency, plat_data->master[6], ++ iommu_profile->micro_tlb[6].max_latency); ++ kfree(iommu_profile); ++ return len; ++} ++ ++ ++static u32 __print_rsv_region(char *buf, size_t buf_len, ssize_t len, ++ struct dump_region *active_region, ++ bool for_sysfs_show) ++{ ++ if (active_region->type == DUMP_REGION_RESERVE) { ++ if (for_sysfs_show) { ++ len += sysfs_emit_at( ++ buf, len, ++ "iova:%pad size:0x%zx\n", ++ &active_region->iova, active_region->size); ++ } else { ++ len += scnprintf( ++ buf + len, buf_len - len, ++ "iova:%pad size:0x%zx\n", ++ &active_region->iova, active_region->size); ++ } ++ } ++ return len; ++} ++ ++u32 sunxi_iommu_dump_rsv_list(struct list_head *rsv_list, ssize_t len, ++ char *buf, size_t buf_len, bool for_sysfs_show) ++{ ++ struct iommu_resv_region *resv; ++ struct dump_region active_region; ++ if (for_sysfs_show) { ++ len += sysfs_emit_at(buf, len, "reserved\n"); ++ } else { ++ len += scnprintf(buf + len, buf_len - len, "reserved\n"); ++ } ++ list_for_each_entry(resv, rsv_list, list) { ++ active_region.access_mask = 0; ++ active_region.iova = resv->start; ++ active_region.type = DUMP_REGION_RESERVE; ++ active_region.size = resv->length; ++ len = __print_rsv_region(buf, buf_len, len, &active_region, ++ for_sysfs_show); ++ } ++ return len; ++} ++ ++static ssize_t sun55i_iommu_dump_pgtable(struct sunxi_iommu_dev *iommu, char *buf, size_t buf_len, ++ bool for_sysfs_show) ++{ ++ struct sunxi_iommu_domain *sunxi_domain = iommu->debug_domain; ++ ssize_t len = 0; ++ ++ len = sunxi_iommu_dump_rsv_list(&iommu->rsv_list, len, buf, ++ buf_len, for_sysfs_show); ++ ++ if (sunxi_domain && sunxi_domain->pgtable) { ++ len = sunxi_pgtable_dump(sunxi_domain->pgtable, len, buf, buf_len, ++ for_sysfs_show); ++ } else { ++ if (for_sysfs_show) { ++ len += sysfs_emit_at(buf, len, "no active domain to dump\n"); ++ } else { ++ len += scnprintf(buf + len, buf_len - len, "no active domain to dump\n"); ++ } ++ } ++ ++ return len; ++} ++ ++static ssize_t sun55i_iommu_map_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct sunxi_iommu_dev *iommu = dev_get_drvdata(dev); ++ ++ if (!iommu) ++ return -ENODEV; ++ ++ return sun55i_iommu_dump_pgtable(iommu, buf, PAGE_SIZE, true); ++} ++ ++static struct device_attribute sunxi_iommu_enable_attr = ++ __ATTR(enable, 0644, sunxi_iommu_enable_show, ++ sunxi_iommu_enable_store); ++static struct device_attribute sunxi_iommu_profilling_attr = ++ __ATTR(profilling, 0444, sunxi_iommu_profilling_show, NULL); ++static struct device_attribute sun55i_iommu_map_attr = ++ __ATTR(page_debug, 0444, sun55i_iommu_map_show, NULL); ++ ++static void sun55i_iommu_sysfs_create(struct platform_device *_pdev, ++ struct sunxi_iommu_dev *sunxi_iommu) ++{ ++ device_create_file(&_pdev->dev, &sunxi_iommu_enable_attr); ++ device_create_file(&_pdev->dev, &sunxi_iommu_profilling_attr); ++ device_create_file(&_pdev->dev, &sun55i_iommu_map_attr); ++} ++ ++static void sun55i_iommu_sysfs_remove(struct platform_device *_pdev) ++{ ++ device_remove_file(&_pdev->dev, &sunxi_iommu_enable_attr); ++ device_remove_file(&_pdev->dev, &sunxi_iommu_profilling_attr); ++ device_remove_file(&_pdev->dev, &sun55i_iommu_map_attr); ++} ++ ++ ++int sunxi_iommu_check_cmd(struct device *dev, void *data) ++{ ++ struct iommu_resv_region *region; ++ int prot = IOMMU_WRITE | IOMMU_READ; ++ struct list_head *rsv_list = data; ++ struct { ++ const char *name; ++ u32 region_type; ++ } supported_region[2] = { { "sunxi-iova-reserve", IOMMU_RESV_RESERVED }, ++ { "sunxi-iova-premap", IOMMU_RESV_DIRECT } }; ++ int i, j; ++#define REGION_CNT_MAX (8) ++ struct { ++ u64 array[REGION_CNT_MAX * 2]; ++ int count; ++ } *tmp_data; ++ ++ tmp_data = kzalloc(sizeof(*tmp_data), GFP_KERNEL); ++ if (!tmp_data) ++ return -ENOMEM; ++ ++ for (i = 0; i < ARRAY_SIZE(supported_region); i++) { ++ /* search all supported argument */ ++ if (!of_find_property(dev->of_node, supported_region[i].name, ++ NULL)) ++ continue; ++ ++ tmp_data->count = of_property_read_variable_u64_array( ++ dev->of_node, supported_region[i].name, tmp_data->array, ++ 0, REGION_CNT_MAX); ++ if (tmp_data->count <= 0) ++ continue; ++ if ((tmp_data->count & 1) != 0) { ++ dev_err(dev, "size %d of array %s should be even\n", ++ tmp_data->count, supported_region[i].name); ++ continue; ++ } ++ ++ /* two u64 describe one region */ ++ tmp_data->count /= 2; ++ ++ /* prepared reserve region data */ ++ for (j = 0; j < tmp_data->count; j++) { ++ region = iommu_alloc_resv_region( ++ tmp_data->array[j * 2], ++ tmp_data->array[j * 2 + 1], prot, ++ supported_region[i].region_type, ++ GFP_KERNEL); ++ if (!region) { ++ dev_err(dev, "no memory for iova rsv region"); ++ } else { ++ struct iommu_resv_region *walk; ++ /* warn on region overlaps */ ++ list_for_each_entry(walk, rsv_list, list) { ++ phys_addr_t walk_end = ++ walk->start + walk->length; ++ phys_addr_t region_end = ++ region->start + region->length; ++ if (!(walk->start > ++ region->start + ++ region->length || ++ walk->start + walk->length < ++ region->start)) { ++ dev_warn( ++ dev, ++ "overlap on iova-reserve %pap~%pap with %pap~%pap", ++ &walk->start, &walk_end, ++ ®ion->start, ++ ®ion_end); ++ } ++ } ++ list_add_tail(®ion->list, rsv_list); ++ } ++ } ++ } ++ kfree(tmp_data); ++#undef REGION_CNT_MAX ++ ++ return 0; ++} ++ ++static int __init_reserve_mem(struct sunxi_iommu_dev *dev) ++{ ++ return bus_for_each_dev(&platform_bus_type, NULL, &dev->rsv_list, ++ sunxi_iommu_check_cmd); ++} ++ ++static const struct iommu_ops sunxi_iommu_ops = { ++ .domain_alloc_paging = sun55i_iommu_domain_alloc_paging, ++ .probe_device = sun55i_iommu_probe_device, ++ .probe_finalize = sun55i_iommu_probe_device_finalize, ++ .release_device = sun55i_iommu_release_device, ++ .device_group = generic_device_group, ++ .of_xlate = sun55i_iommu_of_xlate, ++ .owner = THIS_MODULE, ++ .default_domain_ops = &(const struct iommu_domain_ops) { ++ .attach_dev = sun55i_iommu_attach_dev, ++ .map_pages = sun55i_iommu_map, ++ .unmap_pages = sun55i_iommu_unmap, ++ .iotlb_sync_map = sun55i_iommu_iotlb_sync_map, ++ .iova_to_phys = sun55i_iommu_iova_to_phys, ++ .free = sun55i_iommu_domain_free, ++ } ++}; ++ ++static int sun55i_iommu_probe(struct platform_device *pdev) ++{ ++ int ret, irq; ++ struct device *dev = &pdev->dev; ++ struct sunxi_iommu_dev *sunxi_iommu; ++ struct resource *res; ++ ++ iopte_cache = sunxi_pgtable_alloc_pte_cache(); ++ if (!iopte_cache) { ++ pr_err("%s: Failed to create sunx-iopte-cache.\n", __func__); ++ return -ENOMEM; ++ } ++ ++ sunxi_iommu = devm_kzalloc(dev, sizeof(*sunxi_iommu), GFP_KERNEL); ++ if (!sunxi_iommu) { ++ kmem_cache_destroy(iopte_cache); ++ iopte_cache = NULL; ++ return -ENOMEM; ++ } ++ ++ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ++ if (!res) { ++ dev_dbg(dev, "Unable to find resource region\n"); ++ ret = -ENOENT; ++ goto err_res; ++ } ++ ++ sunxi_iommu->base = devm_ioremap_resource(&pdev->dev, res); ++ if (IS_ERR(sunxi_iommu->base)) { ++ dev_dbg(dev, "Unable to map IOMEM @ PA:%pa\n", &res->start); ++ ret = PTR_ERR(sunxi_iommu->base); ++ goto err_res; ++ } ++ ++ sunxi_iommu->bypass = DEFAULT_BYPASS_VALUE; ++ ++ irq = platform_get_irq(pdev, 0); ++ if (irq <= 0) { ++ dev_dbg(dev, "Unable to find IRQ resource\n"); ++ ret = -ENOENT; ++ goto err_res; ++ } ++ pr_info("sunxi iommu: irq = %d\n", irq); ++ ++ ret = devm_request_irq(dev, irq, sunxi_iommu_irq, 0, ++ dev_name(dev), (void *)sunxi_iommu); ++ if (ret < 0) { ++ dev_dbg(dev, "Unabled to register interrupt handler\n"); ++ goto err_res; ++ } ++ ++ sunxi_iommu->irq = irq; ++ ++ sunxi_iommu->clk = of_clk_get_by_name(dev->of_node, "iommu"); ++ if (IS_ERR(sunxi_iommu->clk)) { ++ sunxi_iommu->clk = NULL; ++ dev_dbg(dev, "Unable to find clock\n"); ++ ret = PTR_ERR(sunxi_iommu->clk); ++ goto err_clk; ++ } ++ clk_prepare_enable(sunxi_iommu->clk); ++ ++ platform_set_drvdata(pdev, sunxi_iommu); ++ sunxi_iommu->dev = dev; ++ spin_lock_init(&sunxi_iommu->iommu_lock); ++ global_iommu_dev = sunxi_iommu; ++ sunxi_iommu->plat_data = of_device_get_match_data(dev); ++ ++ if (sunxi_iommu->plat_data->version != ++ sunxi_iommu_read(sunxi_iommu, IOMMU_VERSION_REG)) { ++ dev_err(dev, "iommu version mismatch, please check and reconfigure\n"); ++ ++ clk_disable_unprepare(sunxi_iommu->clk); ++ ret = -EINVAL; ++ goto err_clk; ++ } ++ ++ sun55i_iommu_sysfs_create(pdev, sunxi_iommu); ++ ret = iommu_device_sysfs_add(&sunxi_iommu->iommu, dev, NULL, ++ dev_name(dev)); ++ if (ret) { ++ dev_err(dev, "Failed to register iommu in sysfs\n"); ++ clk_disable_unprepare(sunxi_iommu->clk); ++ goto err_clk; ++ } ++ ++ ret = iommu_device_register(&sunxi_iommu->iommu, &sunxi_iommu_ops, dev); ++ if (ret) { ++ dev_err(dev, "Failed to register iommu\n"); ++ goto err_sysfs_remove; ++ } ++ ++ INIT_LIST_HEAD(&sunxi_iommu->rsv_list); ++ __init_reserve_mem(sunxi_iommu); ++ ++ sunxi_iommu->identity_domain = sun55i_iommu_domain_alloc_paging(&pdev->dev); ++ if (!sunxi_iommu->identity_domain) { ++ dev_err(dev, "Failed to allocate identity domain\n"); ++ ret = -ENOMEM; ++ goto err_iommu_unregister; ++ } ++ ++ if (!list_empty(&sunxi_iommu->rsv_list)) { ++ struct iommu_resv_region *entry; ++ ++ dev_info(dev, "Mapping %zu reserved regions for identity domain\n", ++ list_count_nodes(&sunxi_iommu->rsv_list)); ++ ++ list_for_each_entry(entry, &sunxi_iommu->rsv_list, list) { ++ size_t size = entry->length; ++ phys_addr_t phys = entry->start; ++ ++ if (sun55i_iommu_map(sunxi_iommu->identity_domain, phys, phys, size, 1, entry->prot, GFP_KERNEL, NULL)) { ++ dev_err(dev, "Failed to map reserved region %pa [%zx]\n", ++ &phys, size); ++ } ++ } ++ } ++ ++ if (!dma_dev) { ++ dma_dev = &pdev->dev; ++ sunxi_pgtable_set_dma_dev(dma_dev); ++ } ++ ++ return 0; ++ ++err_iommu_unregister: ++ iommu_device_unregister(&sunxi_iommu->iommu); ++err_sysfs_remove: ++ iommu_device_sysfs_remove(&sunxi_iommu->iommu); ++err_clk: ++ clk_disable_unprepare(sunxi_iommu->clk); ++err_res: ++ sunxi_pgtable_free_pte_cache(iopte_cache); ++ dev_err(dev, "Failed to initialize\n"); ++ ++ return ret; ++} ++ ++static void sun55i_iommu_remove(struct platform_device *pdev) ++{ ++ struct sunxi_iommu_dev *sunxi_iommu = platform_get_drvdata(pdev); ++ struct iommu_resv_region *entry, *next; ++ ++ sunxi_pgtable_free_pte_cache(iopte_cache); ++ if (!list_empty(&sunxi_iommu->rsv_list)) { ++ list_for_each_entry_safe (entry, next, &sunxi_iommu->rsv_list, ++ list) ++ kfree(entry); ++ } ++ devm_free_irq(sunxi_iommu->dev, sunxi_iommu->irq, sunxi_iommu); ++ devm_iounmap(sunxi_iommu->dev, sunxi_iommu->base); ++ sun55i_iommu_sysfs_remove(pdev); ++ iommu_device_sysfs_remove(&sunxi_iommu->iommu); ++ iommu_device_unregister(&sunxi_iommu->iommu); ++ global_iommu_dev = NULL; ++ ++ return; ++} ++ ++static int sun55i_iommu_suspend(struct device *dev) ++{ ++ clk_disable_unprepare(global_iommu_dev->clk); ++ ++ return 0; ++} ++ ++static int sun55i_iommu_resume(struct device *dev) ++{ ++ struct sunxi_iommu_dev *iommu = dev_get_drvdata(dev); ++ ++ clk_prepare_enable(iommu->clk); ++ ++ return sun55i_iommu_hw_init(iommu, NULL); ++} ++ ++static const struct dev_pm_ops sunxi_iommu_pm_ops = { ++ .suspend = sun55i_iommu_suspend, ++ .resume = sun55i_iommu_resume, ++}; ++ ++static const struct sunxi_iommu_plat_data iommu_v15_sun55iw3_data = { ++ .version = 0x15, ++ /* disable preftech to test display rcq bug */ ++ .tlb_prefetch = 0x30000, ++ .tlb_invalid_mode = 0x1, ++ .ptw_invalid_mode = 0x1, ++ .master = {"ISP", "CSI", "VE0", "VE1", "G2D", "DE", ++ "DI", "DEBUG_MODE"}, ++}; ++ ++static const struct of_device_id sunxi_iommu_dt_ids[] = { ++ { .compatible = "allwinner,sun55i-a523-iommu", .data = &iommu_v15_sun55iw3_data}, ++ { /* sentinel */ }, ++}; ++ ++static struct platform_driver sunxi_iommu_driver = { ++ .probe = sun55i_iommu_probe, ++ .remove = sun55i_iommu_remove, ++ .driver = { ++ .owner = THIS_MODULE, ++ .name = "sunxi-iommu", ++ .pm = &sunxi_iommu_pm_ops, ++ .of_match_table = sunxi_iommu_dt_ids, ++ } ++}; ++ ++static int __init sunxi_iommu_init(void) ++{ ++ return platform_driver_register(&sunxi_iommu_driver); ++} ++ ++static void __exit sunxi_iommu_exit(void) ++{ ++ return platform_driver_unregister(&sunxi_iommu_driver); ++} ++ ++subsys_initcall(sunxi_iommu_init); ++module_exit(sunxi_iommu_exit); +--- /dev/null ++++ b/drivers/iommu/sun55i-iommu.h +@@ -0,0 +1,57 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * sunxi iommu: main structures ++ * ++ * Copyright (C) 2008-2009 Nokia Corporation ++ * ++ * Written by Hiroshi DOYU ++ * ++ * 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 "sun55i-iommu-pgtable.h" ++ ++//iommu domain have seperate ops ++#define SEPERATE_DOMAIN_API ++//dma-iommu is enclosed into iommu-core ++#define DMA_IOMMU_IN_IOMMU ++//not used anywhere since refactoring ++#define GROUP_NOTIFIER_DEPRECATED ++//iommu now have correct probe order ++//no more need bus set op as workaround ++#define BUS_SET_OP_DEPRECATED ++//dma cookie handled by iommu core, not driver ++#define COOKIE_HANDLE_BY_CORE ++//iommu resv region allocation require gfp flags ++#define RESV_REGION_NEED_GFP_FLAG ++ ++#ifdef DMA_IOMMU_IN_IOMMU ++#include ++/* ++ * by design iommu driver should be part of iommu ++ * and get to it by ../../dma-iommu.h ++ * sunxi bsp have seperate root, use different path ++ * to reach dma-iommu.h ++ */ ++#include <../drivers/iommu/dma-iommu.h> ++#else ++#include ++#endif ++ ++#define MAX_SG_SIZE (128 << 20) ++#define MAX_SG_TABLE_SIZE ((MAX_SG_SIZE / SPAGE_SIZE) * sizeof(u32)) ++#define DUMP_REGION_MAP 0 ++#define DUMP_REGION_RESERVE 1 ++struct dump_region { ++ u32 access_mask; ++ size_t size; ++ u32 type; ++ dma_addr_t phys, iova; ++}; ++struct sunxi_iommu_dev; ++void sun55i_reset_device_iommu(unsigned int master_id); ++void sun55i_enable_device_iommu(struct sunxi_iommu_dev *iommu, unsigned int master_id, bool flag); +--- /dev/null ++++ b/include/sunxi-iommu.h +@@ -0,0 +1,50 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * ++ * Copyright (C) 2015 AllWinnertech Ltd. ++ * ++ * Author: huangshuosheng ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ */ ++ ++#ifndef __LINUX_SUNXI_IOMMU_H ++#define __LINUX_SUNXI_IOMMU_H ++#include ++#include ++ ++struct sunxi_iommu_dev; ++typedef void (*sunxi_iommu_fault_cb)(void); ++extern void sun55i_iommu_register_fault_cb(sunxi_iommu_fault_cb cb, unsigned int master_id); ++extern void sun55i_enable_device_iommu(struct sunxi_iommu_dev *iommu, unsigned int master_id, bool flag); ++extern void sun55i_reset_device_iommu(unsigned int master_id); ++ ++enum iommu_dma_cookie_type { ++ IOMMU_DMA_IOVA_COOKIE, ++ IOMMU_DMA_MSI_COOKIE, ++}; ++ ++struct iommu_dma_cookie { ++ enum iommu_dma_cookie_type type; ++ union { ++ /* Full allocator for IOMMU_DMA_IOVA_COOKIE */ ++ struct iova_domain iovad; ++ /* Trivial linear page allocator for IOMMU_DMA_MSI_COOKIE */ ++ dma_addr_t msi_iova; ++ }; ++ struct list_head msi_page_list; ++ ++ /* Domain for flush queue callback; NULL if flush queue not in use */ ++ struct iommu_domain *fq_domain; ++}; ++ ++#endif /* __LINUX_SUNXI_IOMMU_H */ diff --git a/target/linux/sunxi/patches-6.18/125-phy-allwinner-add-pcie-usb3-driver.patch b/target/linux/sunxi/patches-6.18/125-phy-allwinner-add-pcie-usb3-driver.patch new file mode 100644 index 0000000000..b58cbbbb46 --- /dev/null +++ b/target/linux/sunxi/patches-6.18/125-phy-allwinner-add-pcie-usb3-driver.patch @@ -0,0 +1,1229 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marvin Wewer +Date: Fri, 27 Jun 2025 12:40:09 +0200 +Subject: phy: allwinner: add INNO PCIe/USB3 combo PHY driver + +Signed-off-by: Marvin Wewer +--- + drivers/phy/allwinner/Kconfig | 8 + + drivers/phy/allwinner/Makefile | 1 + + drivers/phy/allwinner/sunxi-inno-combophy.c | 1193 ++++++++++ + 3 files changed, 1202 insertions(+) + +--- a/drivers/phy/allwinner/Kconfig ++++ b/drivers/phy/allwinner/Kconfig +@@ -57,3 +57,11 @@ config PHY_SUN50I_USB3 + part of Allwinner H6 SoC. + + This driver controls each individual USB 2+3 host PHY combo. ++ ++config AW_INNO_COMBOPHY ++ tristate "Allwinner INNO COMBO PHY Driver" ++ depends on ARCH_SUNXI && OF ++ select GENERIC_PHY ++ help ++ Enable this to support the Allwinner PCIe/USB3.0 combo PHY ++ with INNOSILICON IP block. +--- a/drivers/phy/allwinner/Makefile ++++ b/drivers/phy/allwinner/Makefile +@@ -3,3 +3,4 @@ obj-$(CONFIG_PHY_SUN4I_USB) += phy-sun4 + obj-$(CONFIG_PHY_SUN6I_MIPI_DPHY) += phy-sun6i-mipi-dphy.o + obj-$(CONFIG_PHY_SUN9I_USB) += phy-sun9i-usb.o + obj-$(CONFIG_PHY_SUN50I_USB3) += phy-sun50i-usb3.o ++obj-$(CONFIG_AW_INNO_COMBOPHY) += sunxi-inno-combophy.o +--- /dev/null ++++ b/drivers/phy/allwinner/sunxi-inno-combophy.c +@@ -0,0 +1,1193 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Allwinner PIPE USB3.0 PCIE Combo Phy driver ++ * ++ * Copyright (C) 2022 Allwinner Electronics Co., Ltd. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++/* PCIE USB3 Sub-System Registers */ ++/* Sub-System Version Reset Register */ ++#define PCIE_USB3_SYS_VER 0x00 ++ ++/* Sub-System PCIE Bus Gating Reset Register */ ++#define PCIE_COMBO_PHY_BGR 0x04 ++#define PCIE_SLV_ACLK_EN BIT(18) ++#define PCIE_ACLK_EN BIT(17) ++#define PCIE_HCLK_EN BIT(16) ++#define PCIE_PERSTN BIT(1) ++#define PCIE_PW_UP_RSTN BIT(0) ++ ++/* Sub-System USB3 Bus Gating Reset Register */ ++#define USB3_COMBO_PHY_BGR 0x08 ++#define USB3_ACLK_EN BIT(17) ++#define USB3_HCLK_EN BIT(16) ++#define USB3_U2_PHY_RSTN BIT(4) ++#define USB3_U2_PHY_MUX_EN BIT(3) ++#define USB3_U2_PHY_MUX_SEL BIT(0) ++#define USB3_RESETN BIT(0) ++ ++/* Sub-System PCIE PHY Control Register */ ++#define PCIE_COMBO_PHY_CTL 0x10 ++#define PHY_USE_SEL BIT(31) /* 0:PCIE; 1:USB3 */ ++#define PHY_CLK_SEL BIT(30) /* 0:internal clk; 1:external clk */ ++#define PHY_BIST_EN BIT(16) ++#define PHY_PIPE_SW BIT(9) ++#define PHY_PIPE_SEL BIT(8) /* 0:rstn by PCIE or USB3; 1:rstn by PHY_PIPE_SW */ ++#define PHY_PIPE_CLK_INVERT BIT(4) ++#define PHY_FPGA_SYS_RSTN BIT(1) /* for FPGA */ ++#define PHY_RSTN BIT(0) ++ ++/* Registers */ ++#define COMBO_REG_SYSVER(comb_base_addr) ((comb_base_addr) \ ++ + PCIE_USB3_SYS_VER) ++#define COMBO_REG_PCIEBGR(comb_base_addr) ((comb_base_addr) \ ++ + PCIE_COMBO_PHY_BGR) ++#define COMBO_REG_USB3BGR(comb_base_addr) ((comb_base_addr) \ ++ + USB3_COMBO_PHY_BGR) ++#define COMBO_REG_PHYCTRL(comb_base_addr) ((comb_base_addr) \ ++ + PCIE_COMBO_PHY_CTL) ++/* Sub-System Version Number */ ++#define COMBO_VERSION_01 (0x10000) ++#define COMBO_VERSION_ANY (0x0) ++ ++#define KEY_PHY_USE_SEL "phy_use_sel" ++#define KEY_PHY_REFCLK_SEL "phy_refclk_sel" ++ ++enum phy_use_sel { ++ PHY_USE_BY_PCIE = 0, /* PHY used by PCIE */ ++ PHY_USE_BY_USB3, /* PHY used by USB3 */ ++ PHY_USE_BY_PCIE_USB3_U2,/* PHY used by PCIE & USB3_U2 */ ++}; ++ ++enum phy_refclk_sel { ++ INTER_SIG_REF_CLK = 0, /* PHY use internal single end reference clock */ ++ EXTER_DIF_REF_CLK, /* PHY use external single end reference clock */ ++}; ++ ++extern struct atomic_notifier_head inno_subsys_notifier_list; ++ ++struct sunxi_combophy_of_data { ++ bool has_cfg_clk; ++ bool has_slv_clk; ++ bool has_phy_mbus_clk; ++ bool has_phy_ahb_clk; ++ bool has_pcie_axi_clk; ++ bool has_u2_phy_mux; ++ bool need_noppu_rst; ++ bool has_u3_phy_data_quirk; ++ bool need_optimize_jitter; ++}; ++ ++struct sunxi_combphy { ++ struct device *dev; ++ struct phy *phy; ++ void __iomem *phy_ctl; /* parse dts, control the phy mode, reset and power */ ++ void __iomem *phy_clk; /* parse dts, set the phy clock */ ++ struct reset_control *reset; ++ struct reset_control *noppu_reset; ++ ++ struct clk *phyclk_ref; ++ struct clk *refclk_par; ++ struct clk *phyclk_cfg; ++ struct clk *cfgclk_par; ++ struct clk *phy_mclk; ++ struct clk *phy_hclk; ++ struct clk *phy_axi; ++ struct clk *phy_axi_par; ++ __u8 mode; ++ __u32 vernum; /* version number */ ++ enum phy_use_sel user; ++ enum phy_refclk_sel ref; ++ struct notifier_block pwr_nb; ++ const struct sunxi_combophy_of_data *drvdata; ++ ++ struct regulator *select3v3_supply; ++ bool initialized; ++}; ++ ++ATOMIC_NOTIFIER_HEAD(inno_subsys_notifier_list); ++EXPORT_SYMBOL(inno_subsys_notifier_list); ++ ++/* PCIE USB3 Sub-system Application */ ++static void combo_pcie_clk_set(struct sunxi_combphy *combphy, bool enable) ++{ ++ u32 val, tmp = 0; ++ ++ val = readl(COMBO_REG_PCIEBGR(combphy->phy_ctl)); ++ if (combphy->drvdata->has_slv_clk) ++ tmp = PCIE_SLV_ACLK_EN | PCIE_ACLK_EN | PCIE_HCLK_EN | PCIE_PERSTN | PCIE_PW_UP_RSTN; ++ else ++ tmp = PCIE_ACLK_EN | PCIE_HCLK_EN | PCIE_PERSTN | PCIE_PW_UP_RSTN; ++ if (enable) ++ val |= tmp; ++ else ++ val &= ~tmp; ++ writel(val, COMBO_REG_PCIEBGR(combphy->phy_ctl)); ++} ++ ++static void combo_usb3_clk_set(struct sunxi_combphy *combphy, bool enable) ++{ ++ u32 val, tmp = 0; ++ ++ val = readl(COMBO_REG_USB3BGR(combphy->phy_ctl)); ++ if (combphy->drvdata->has_u2_phy_mux) ++ tmp = USB3_ACLK_EN | USB3_HCLK_EN | USB3_U2_PHY_MUX_SEL | USB3_U2_PHY_RSTN | USB3_U2_PHY_MUX_EN; ++ else ++ tmp = USB3_ACLK_EN | USB3_HCLK_EN | USB3_RESETN; ++ if (enable) ++ val |= tmp; ++ else ++ val &= ~tmp; ++ writel(val, COMBO_REG_USB3BGR(combphy->phy_ctl)); ++} ++ ++static void combo_phy_mode_set(struct sunxi_combphy *combphy, bool enable) ++{ ++ u32 val; ++ ++ val = readl(COMBO_REG_PHYCTRL(combphy->phy_ctl)); ++ ++ if (combphy->user == PHY_USE_BY_PCIE || combphy->user == PHY_USE_BY_PCIE_USB3_U2) ++ val &= ~PHY_USE_SEL; ++ else if (combphy->user == PHY_USE_BY_USB3) ++ val |= PHY_USE_SEL; ++ ++ if (combphy->ref == INTER_SIG_REF_CLK) ++ val &= ~PHY_CLK_SEL; ++ else if (combphy->ref == EXTER_DIF_REF_CLK) ++ val |= PHY_CLK_SEL; ++ ++ if (enable) ++ val |= PHY_RSTN; ++ else ++ val &= ~PHY_RSTN; ++ ++ writel(val, COMBO_REG_PHYCTRL(combphy->phy_ctl)); ++} ++ ++static u32 combo_sysver_get(struct sunxi_combphy *combphy) ++{ ++ u32 reg; ++ ++ reg = readl(COMBO_REG_SYSVER(combphy->phy_ctl)); ++ ++ return reg; ++} ++ ++static void pcie_usb3_sub_system_enable(struct sunxi_combphy *combphy) ++{ ++ combo_phy_mode_set(combphy, true); ++ ++ if (combphy->user == PHY_USE_BY_PCIE) ++ combo_pcie_clk_set(combphy, true); ++ else if (combphy->user == PHY_USE_BY_USB3) ++ combo_usb3_clk_set(combphy, true); ++ else if (combphy->user == PHY_USE_BY_PCIE_USB3_U2) { ++ combo_pcie_clk_set(combphy, true); ++ combo_usb3_clk_set(combphy, true); ++ } ++ ++ combphy->vernum = combo_sysver_get(combphy); ++} ++ ++static void pcie_usb3_sub_system_disable(struct sunxi_combphy *combphy) ++{ ++ combo_phy_mode_set(combphy, false); ++ ++ if (combphy->user == PHY_USE_BY_PCIE) ++ combo_pcie_clk_set(combphy, false); ++ else if (combphy->user == PHY_USE_BY_USB3) ++ combo_usb3_clk_set(combphy, false); ++ else if (combphy->user == PHY_USE_BY_PCIE_USB3_U2) { ++ combo_pcie_clk_set(combphy, false); ++ combo_usb3_clk_set(combphy, false); ++ } ++} ++ ++static int sunxi_combphy_enable_clocks(struct sunxi_combphy *combphy) ++{ ++ int ret; ++ struct device *dev = combphy->dev; ++ ++ if (!IS_ERR_OR_NULL(combphy->phyclk_ref)) { ++ ret = clk_prepare_enable(combphy->phyclk_ref); ++ if (ret) return ret; ++ } ++ ++ if (combphy->drvdata->has_cfg_clk && !IS_ERR_OR_NULL(combphy->phyclk_cfg)) { ++ ret = clk_prepare_enable(combphy->phyclk_cfg); ++ if (ret) goto err_cfg; ++ } ++ ++ if (combphy->drvdata->has_phy_ahb_clk && !IS_ERR_OR_NULL(combphy->phy_hclk)) { ++ ret = clk_prepare_enable(combphy->phy_hclk); ++ if (ret) { ++ dev_err(dev, "cannot enable phy_hclk\n"); ++ goto err_hclk; ++ } ++ } ++ ++ if (combphy->drvdata->has_pcie_axi_clk && !IS_ERR_OR_NULL(combphy->phy_axi)) { ++ ret = clk_prepare_enable(combphy->phy_axi); ++ if (ret) { ++ dev_err(dev, "cannot enable phy_axi\n"); ++ goto err_axi; ++ } ++ } ++ ++ if (combphy->drvdata->has_phy_mbus_clk && !IS_ERR_OR_NULL(combphy->phy_mclk)) { ++ ret = clk_prepare_enable(combphy->phy_mclk); ++ if (ret) { ++ dev_err(dev, "cannot enable phy_mclk\n"); ++ goto err_mclk; ++ } ++ } ++ ++ return 0; ++ ++err_mclk: ++ clk_disable_unprepare(combphy->phy_axi); ++err_axi: ++ clk_disable_unprepare(combphy->phy_hclk); ++err_hclk: ++ clk_disable_unprepare(combphy->phyclk_cfg); ++err_cfg: ++ clk_disable_unprepare(combphy->phyclk_ref); ++ return ret; ++} ++ ++static int sunxi_combphy_reset_deassert(struct sunxi_combphy *combphy) ++{ ++ int ret; ++ ++ if (!IS_ERR_OR_NULL(combphy->reset)) { ++ ret = reset_control_deassert(combphy->reset); ++ if (ret) ++ return ret; ++ } ++ ++ if (combphy->drvdata->need_noppu_rst && !IS_ERR_OR_NULL(combphy->noppu_reset)) { ++ ret = reset_control_deassert(combphy->noppu_reset); ++ if (ret) { ++ if (!IS_ERR_OR_NULL(combphy->reset)) ++ reset_control_assert(combphy->reset); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static int pcie_usb3_sub_system_init(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct sunxi_combphy *combphy = platform_get_drvdata(pdev); ++ bool already_enabled = false; ++ int ret; ++ ++ if (!combphy || combphy->initialized) ++ return 0; ++ ++ ++ if (combphy->phy_ctl) { ++ if (readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL) & PHY_RSTN) { ++ dev_info(dev, "PHY already enabled by Bootloader.\n"); ++ already_enabled = true; ++ } ++ } ++ ++ ++ if (!IS_ERR_OR_NULL(combphy->select3v3_supply)) { ++ ret = regulator_enable(combphy->select3v3_supply); ++ if (ret) return ret; ++ } ++ ++ ++ if (!already_enabled) { ++ ++ ret = sunxi_combphy_reset_deassert(combphy); ++ if (ret) goto err_regulator; ++ ++ ++ if (!IS_ERR_OR_NULL(combphy->phyclk_ref)) ++ clk_set_rate(combphy->phyclk_ref, 100000000); ++ ++ if (combphy->drvdata->has_cfg_clk && !IS_ERR_OR_NULL(combphy->phyclk_cfg)) ++ clk_set_rate(combphy->phyclk_cfg, 200000000); ++ } ++ ++ ++ ret = sunxi_combphy_enable_clocks(combphy); ++ if (ret) goto err_regulator; ++ ++ ++ if (!already_enabled) ++ pcie_usb3_sub_system_enable(combphy); ++ ++ combphy->initialized = true; ++ return 0; ++ ++err_regulator: ++ if (!IS_ERR_OR_NULL(combphy->select3v3_supply)) ++ regulator_disable(combphy->select3v3_supply); ++ return ret; ++} ++ ++static int pcie_usb3_sub_system_exit(struct platform_device *pdev) ++{ ++ struct sunxi_combphy *combphy = platform_get_drvdata(pdev); ++ ++ if (!combphy || !combphy->initialized) ++ return 0; ++ ++ ++ pcie_usb3_sub_system_disable(combphy); ++ ++ ++ if (combphy->drvdata->has_phy_mbus_clk && !IS_ERR_OR_NULL(combphy->phy_mclk)) ++ clk_disable_unprepare(combphy->phy_mclk); ++ ++ if (combphy->drvdata->has_pcie_axi_clk && !IS_ERR_OR_NULL(combphy->phy_axi)) ++ clk_disable_unprepare(combphy->phy_axi); ++ ++ if (combphy->drvdata->has_phy_ahb_clk && !IS_ERR_OR_NULL(combphy->phy_hclk)) ++ clk_disable_unprepare(combphy->phy_hclk); ++ ++ if (combphy->drvdata->has_cfg_clk && !IS_ERR_OR_NULL(combphy->phyclk_cfg)) ++ clk_disable_unprepare(combphy->phyclk_cfg); ++ ++ if (!IS_ERR_OR_NULL(combphy->phyclk_ref)) ++ clk_disable_unprepare(combphy->phyclk_ref); ++ ++ ++ if (combphy->drvdata->need_noppu_rst && !IS_ERR_OR_NULL(combphy->noppu_reset)) ++ reset_control_assert(combphy->noppu_reset); ++ ++ if (!IS_ERR_OR_NULL(combphy->reset)) ++ reset_control_assert(combphy->reset); ++ ++ ++ if (!IS_ERR_OR_NULL(combphy->select3v3_supply)) ++ regulator_disable(combphy->select3v3_supply); ++ ++ combphy->initialized = false; ++ return 0; ++} ++ ++static int sunxi_inno_combophy_power_event(struct notifier_block *nb, unsigned long event, void *p) ++{ ++ struct sunxi_combphy *combphy = container_of(nb, struct sunxi_combphy, pwr_nb); ++ struct platform_device *pdev = to_platform_device(combphy->dev); ++ ++ dev_dbg(combphy->dev, "event %s\n", event ? "on" : "off"); ++ if (event) { ++ if (combphy->initialized) { ++ pcie_usb3_sub_system_exit(pdev); ++ } ++ pcie_usb3_sub_system_init(pdev); ++ } ++ else ++ pcie_usb3_sub_system_exit(pdev); ++ ++ return NOTIFY_DONE; ++} ++ ++static void sunxi_combphy_pcie_phy_enable(struct sunxi_combphy *combphy) ++{ ++ u32 val; ++ ++ /* Enable clocks and power for PCIe */ ++ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_BGR); ++ val &= ~((0x03 << 0) | (0x07 << 16)); /* Clear power/reset and clock bits */ ++ val |= (0x03 << 0); /* Set PCIE_PERSTN and PCIE_PW_UP_RSTN */ ++ if (combphy->drvdata->has_slv_clk) ++ val |= (0x07 << 16); /* Enable all clocks: SLV_ACLK, ACLK, HCLK */ ++ else ++ val |= (0x03 << 16); /* Enable ACLK, HCLK */ ++ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_BGR); ++ ++ /* Assert PHY reset */ ++ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL); ++ val &= ~PHY_USE_SEL; /* Select PCIe mode */ ++ val &= ~(0x03 << 8); /* Clear PHY_PIPE_SEL and PHY_PIPE_SW */ ++ val &= ~PHY_RSTN; /* Assert PHY reset */ ++ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_CTL); ++ ++ /* Wait for reset to propagate */ ++ udelay(10); ++ ++ /* De-assert PHY reset */ ++ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL); ++ val &= ~PHY_CLK_SEL; /* Select internal clock */ ++ val |= PHY_RSTN; /* De-assert PHY reset */ ++ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_CTL); ++} ++ ++static void sunxi_combphy_usb3_phy_set(struct sunxi_combphy *combphy, bool enable) ++{ ++ u32 val, tmp = 0; ++ ++ val = readl(combphy->phy_clk + 0x1418); ++ tmp = GENMASK(17, 16); ++ if (enable) { ++ val &= ~tmp; ++ val |= BIT(25); ++ } else { ++ val |= tmp; ++ val &= ~BIT(25); ++ } ++ writel(val, combphy->phy_clk + 0x1418); ++ ++ /* reg_rx_eq_bypass[3]=1, rx_ctle_res_cal_bypass */ ++ val = readl(combphy->phy_clk + 0x0674); ++ if (enable) ++ val |= BIT(3); ++ else ++ val &= ~BIT(3); ++ writel(val, combphy->phy_clk + 0x0674); ++ ++ /* rx_ctle_res_cal=0xf, 0x4->0xf */ ++ val = readl(combphy->phy_clk + 0x0704); ++ tmp = GENMASK(9, 8) | BIT(11); ++ if (enable) ++ val |= tmp; ++ else ++ val &= ~tmp; ++ writel(val, combphy->phy_clk + 0x0704); ++ ++ /* CDR_div_fin_gain1 */ ++ val = readl(combphy->phy_clk + 0x0400); ++ if (enable) ++ val |= BIT(4); ++ else ++ val &= ~BIT(4); ++ writel(val, combphy->phy_clk + 0x0400); ++ ++ /* CDR_div1_fin_gain1 */ ++ val = readl(combphy->phy_clk + 0x0404); ++ tmp = GENMASK(3, 0) | BIT(5); ++ if (enable) ++ val |= tmp; ++ else ++ val &= ~tmp; ++ writel(val, combphy->phy_clk + 0x0404); ++ ++ /* CDR_div3_fin_gain1 */ ++ val = readl(combphy->phy_clk + 0x0408); ++ if (enable) ++ val |= BIT(5); ++ else ++ val &= ~BIT(5); ++ writel(val, combphy->phy_clk + 0x0408); ++ ++ val = readl(combphy->phy_clk + 0x109c); ++ if (enable) ++ val |= BIT(1); ++ else ++ val &= ~BIT(1); ++ writel(val, combphy->phy_clk + 0x109c); ++ ++ /* balance parm configure */ ++ if (combphy->drvdata->has_u3_phy_data_quirk) { ++ val = readl(combphy->phy_clk + 0x0804); ++ if (enable) ++ val |= (0x6<<4); ++ else ++ val &= ~(0xf<<4); ++ writel(val, combphy->phy_clk + 0x0804); ++ } ++ ++ /* SSC configure */ ++ val = readl(combphy->phy_clk + 0x107c); ++ tmp = 0x3f << 12; ++ val = val & (~tmp); ++ val |= ((0x1 << 12) & tmp); /* div_N */ ++ writel(val, combphy->phy_clk + 0x107c); ++ ++ val = readl(combphy->phy_clk + 0x1020); ++ tmp = 0x1f << 0; ++ val = val & (~tmp); ++ val |= ((0x6 << 0) & tmp); /* modulation freq div */ ++ writel(val, combphy->phy_clk + 0x1020); ++ ++ val = readl(combphy->phy_clk + 0x1034); ++ tmp = 0x7f << 16; ++ val = val & (~tmp); ++ val |= ((0x9 << 16) & tmp); /* spread[6:0], 400*9=4410ppm ssc */ ++ writel(val, combphy->phy_clk + 0x1034); ++ ++ val = readl(combphy->phy_clk + 0x101c); ++ tmp = 0x1 << 27; ++ val = val & (~tmp); ++ val |= ((0x1 << 27) & tmp); /* choose downspread */ ++ ++ tmp = 0x1 << 28; ++ val = val & (~tmp); ++ if (enable) ++ val |= ((0x0 << 28) & tmp); /* don't disable ssc = 0 */ ++ else ++ val |= ((0x1 << 28) & tmp); /* don't enable ssc = 1 */ ++ writel(val, combphy->phy_clk + 0x101c); ++ ++#ifdef SUNXI_INNO_COMMBOPHY_DEBUG ++ /* TX Eye configure bypass_en */ ++ val = readl(combphy->phy_clk + 0x0ddc); ++ if (enable) ++ val |= BIT(4); /* 0x0ddc[4]=1 */ ++ else ++ val &= ~BIT(4); ++ writel(val, combphy->phy_clk + 0x0ddc); ++ ++ /* Leg_cur[6:0] - 7'd84 */ ++ val = readl(combphy->phy_clk + 0x0ddc); ++ val |= ((0x54 & BIT(6)) >> 3); /* 0x0ddc[3] */ ++ writel(val, combphy->phy_clk + 0x0ddc); ++ ++ val = readl(combphy->phy_clk + 0x0de0); ++ val |= ((0x54 & GENMASK(5, 0)) << 2); /* 0x0de0[7:2] */ ++ writel(val, combphy->phy_clk + 0x0de0); ++ ++ /* Leg_curb[5:0] - 6'd18 */ ++ val = readl(combphy->phy_clk + 0x0de4); ++ val |= ((0x12 & GENMASK(5, 1)) >> 1); /* 0x0de4[4:0] */ ++ writel(val, combphy->phy_clk + 0x0de4); ++ ++ val = readl(combphy->phy_clk + 0x0de8); ++ val |= ((0x12 & BIT(0)) << 7); /* 0x0de8[7] */ ++ writel(val, combphy->phy_clk + 0x0de8); ++ ++ /* Exswing_isel */ ++ val = readl(combphy->phy_clk + 0x0028); ++ val |= (0x4 << 28); /* 0x28[30:28] */ ++ writel(val, combphy->phy_clk + 0x0028); ++ ++ /* Exswing_en */ ++ val = readl(combphy->phy_clk + 0x0028); ++ if (enable) ++ val |= BIT(31); /* 0x28[31]=1 */ ++ else ++ val &= ~BIT(31); ++ writel(val, combphy->phy_clk + 0x0028); ++#endif ++} ++ ++static void sunxi_combphy_usb3_power_set(struct sunxi_combphy *combphy, bool enable) ++{ ++ u32 val; ++ ++ dev_dbg(combphy->dev, "set power %s\n", enable ? "on" : "off"); ++ val = readl(combphy->phy_clk + 0x14); ++ if (enable) ++ val &= ~BIT(26); ++ else ++ val |= BIT(26); ++ writel(val, combphy->phy_clk + 0x14); ++ ++ val = readl(combphy->phy_clk + 0x0); ++ if (enable) ++ val &= ~BIT(10); ++ else ++ val |= BIT(10); ++ writel(val, combphy->phy_clk + 0x0); ++} ++ ++static void sunxi_combphy_pcie_phy_100M(struct sunxi_combphy *combphy) ++{ ++ u32 val; ++ ++ val = readl(combphy->phy_clk + 0x1004); ++ val &= ~(0x3<<3); ++ val &= ~(0x1<<0); ++ val |= (0x1<<0); ++ val |= (0x1<<2); ++ val |= (0x1<<4); ++ writel(val, combphy->phy_clk + 0x1004); ++ ++ val = readl(combphy->phy_clk + 0x1018); ++ val &= ~(0x3<<4); ++ val |= (0x3<<4); ++ writel(val, combphy->phy_clk + 0x1018); ++ ++ val = readl(combphy->phy_clk + 0x101c); ++ val &= ~(0x0fffffff); ++ writel(val, combphy->phy_clk + 0x101c); ++ ++ /* if need optimize jitter parm*/ ++ if (combphy->drvdata->need_optimize_jitter) { ++ val = readl(combphy->phy_clk + 0x107c); ++ val &= ~(0x3ffff); ++ val |= (0x4<<12); ++ val |= 0x64; ++ writel(val, combphy->phy_clk + 0x107c); ++ ++ val = readl(combphy->phy_clk + 0x1030); ++ val &= ~(0x3<<20); ++ writel(val, combphy->phy_clk + 0x1030); ++ ++ val = readl(combphy->phy_clk + 0x1050); ++ val &= ~(0x7<<0); ++ val &= ~(0x7<<5); ++ val &= ~(0x3<<3); ++ val |= (0x3<<3); ++ writel(val, combphy->phy_clk + 0x1050); ++ } else { ++ val = readl(combphy->phy_clk + 0x107c); ++ val &= ~(0x3ffff); ++ val |= (0x2<<12); ++ val |= 0x32; ++ writel(val, combphy->phy_clk + 0x107c); ++ ++ val = readl(combphy->phy_clk + 0x1030); ++ val &= ~(0x3<<20); ++ writel(val, combphy->phy_clk + 0x1030); ++ ++ val = readl(combphy->phy_clk + 0x1050); ++ val &= ~(0x7<<5); ++ val |= (0x1<<5); ++ writel(val, combphy->phy_clk + 0x1050); ++ } ++ ++ val = readl(combphy->phy_clk + 0x1054); ++ val &= ~(0x7<<5); ++ val |= (0x1<<5); ++ writel(val, combphy->phy_clk + 0x1054); ++ ++ val = readl(combphy->phy_clk + 0x0804); ++ val &= ~(0xf<<4); ++ val |= (0xc<<4); ++ writel(val, combphy->phy_clk + 0x0804); ++ ++ val = readl(combphy->phy_clk + 0x109c); ++ val &= ~(0x3<<8); ++ val |= (0x1<<1); ++ writel(val, combphy->phy_clk + 0x109c); ++ ++ writel(0x80540a0a, combphy->phy_clk + 0x1418); ++} ++ ++static int sunxi_combphy_pcie_init(struct sunxi_combphy *combphy) ++{ ++ sunxi_combphy_pcie_phy_100M(combphy); ++ ++ sunxi_combphy_pcie_phy_enable(combphy); ++ ++ return 0; ++} ++ ++static int sunxi_combphy_pcie_exit(struct sunxi_combphy *combphy) ++{ ++ u32 val; ++ ++ /* set the phy: ++ * bit(17): aclk enable ++ * bit(16): hclk enbale ++ * bit(1) : pcie_presetn ++ * bit(0) : pcie_power_up_rstn ++ */ ++ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_BGR); ++ val &= (~(0x03<<0)); ++ val &= (~(0x03<<16)); ++ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_BGR); ++ ++ /* Assert the phy */ ++ val = readl(combphy->phy_ctl + PCIE_COMBO_PHY_CTL); ++ val &= (~PHY_USE_SEL); ++ val &= (~(0x03<<8)); ++ val &= (~PHY_RSTN); ++ writel(val, combphy->phy_ctl + PCIE_COMBO_PHY_CTL); ++ ++ return 0; ++} ++ ++static int sunxi_combphy_usb3_init(struct sunxi_combphy *combphy) ++{ ++ sunxi_combphy_usb3_phy_set(combphy, true); ++ ++ return 0; ++} ++ ++static int sunxi_combphy_usb3_exit(struct sunxi_combphy *combphy) ++{ ++ sunxi_combphy_usb3_phy_set(combphy, false); ++ ++ return 0; ++} ++ ++static int sunxi_combphy_usb3_power_on(struct sunxi_combphy *combphy) ++{ ++ int ret; ++ ++ sunxi_combphy_usb3_power_set(combphy, true); ++ ++ if (combphy->select3v3_supply) { ++ ret = regulator_set_voltage(combphy->select3v3_supply, 3300000, 3300000); ++ if (ret) { ++ dev_err(combphy->dev, "set select3v3-supply failed\n"); ++ goto err0; ++ } ++ ++ ret = regulator_enable(combphy->select3v3_supply); ++ if (ret) { ++ dev_err(combphy->dev, "enable select3v3-supply failed\n"); ++ goto err0; ++ } ++ } ++ ++ return 0; ++err0: ++ sunxi_combphy_usb3_power_set(combphy, false); ++ ++ return ret; ++} ++ ++static int sunxi_combphy_usb3_power_off(struct sunxi_combphy *combphy) ++{ ++ sunxi_combphy_usb3_power_set(combphy, false); ++ ++ if (combphy->select3v3_supply) ++ regulator_disable(combphy->select3v3_supply); ++ ++ return 0; ++} ++ ++static int sunxi_combphy_set_mode(struct sunxi_combphy *combphy) ++{ ++ switch (combphy->mode) { ++ case PHY_TYPE_PCIE: ++ sunxi_combphy_pcie_init(combphy); ++ break; ++ case PHY_TYPE_USB3: ++ if (combphy->user == PHY_USE_BY_PCIE_USB3_U2) { ++ sunxi_combphy_pcie_init(combphy); ++ } else if (combphy->user == PHY_USE_BY_USB3) { ++ sunxi_combphy_usb3_init(combphy); ++ } ++ break; ++ default: ++ dev_err(combphy->dev, "incompatible PHY type\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int sunxi_combphy_init(struct phy *phy) ++{ ++ struct sunxi_combphy *combphy = phy_get_drvdata(phy); ++ int ret; ++ ++ ret = sunxi_combphy_set_mode(combphy); ++ if (ret) { ++ dev_err(combphy->dev, "invalid number of arguments\n"); ++ return ret; ++ } ++ ++ return ret; ++} ++ ++static int sunxi_combphy_exit(struct phy *phy) ++{ ++ struct sunxi_combphy *combphy = phy_get_drvdata(phy); ++ ++ switch (combphy->mode) { ++ case PHY_TYPE_PCIE: ++ sunxi_combphy_pcie_exit(combphy); ++ break; ++ case PHY_TYPE_USB3: ++ sunxi_combphy_usb3_exit(combphy); ++ break; ++ default: ++ dev_err(combphy->dev, "incompatible PHY type\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int sunxi_combphy_power_on(struct phy *phy) ++{ ++ struct sunxi_combphy *combphy = phy_get_drvdata(phy); ++ int ret; ++ switch (combphy->mode) { ++ case PHY_TYPE_PCIE: ++ break; ++ case PHY_TYPE_USB3: ++ ret = sunxi_combphy_usb3_power_on(combphy); ++ if (ret) ++ return ret; ++ break; ++ default: ++ dev_err(combphy->dev, "incompatible PHY type\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int sunxi_combphy_power_off(struct phy *phy) ++{ ++ struct sunxi_combphy *combphy = phy_get_drvdata(phy); ++ int ret; ++ ++ switch (combphy->mode) { ++ case PHY_TYPE_PCIE: ++ break; ++ case PHY_TYPE_USB3: ++ ret = sunxi_combphy_usb3_power_off(combphy); ++ if (ret) ++ return ret; ++ break; ++ default: ++ dev_err(combphy->dev, "incompatible PHY type\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static const struct phy_ops sunxi_combphy_ops = { ++ .init = sunxi_combphy_init, ++ .exit = sunxi_combphy_exit, ++ .power_on = sunxi_combphy_power_on, ++ .power_off = sunxi_combphy_power_off, ++ .owner = THIS_MODULE, ++}; ++ ++static struct phy *sunxi_combphy_xlate(struct device *dev, ++ const struct of_phandle_args *args) ++{ ++ struct sunxi_combphy *combphy = dev_get_drvdata(dev); ++ ++ if (args->args_count != 1) { ++ dev_err(dev, "invalid number of arguments\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ if (combphy->mode != PHY_NONE && combphy->mode != args->args[0]) ++ dev_warn(dev, "phy type select %d overwriting type %d\n", ++ args->args[0], combphy->mode); ++ ++ combphy->mode = args->args[0]; ++ ++ return combphy->phy; ++} ++ ++static int sunxi_combphy_parse_dt(struct platform_device *pdev, ++ struct sunxi_combphy *combphy) ++{ ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; ++ int ret = -1; ++ struct resource *res_ctl; ++ struct resource *res_clk; ++ ++ /* combo phy use sel */ ++ ret = of_property_read_u32(np, KEY_PHY_USE_SEL, &combphy->user); ++ if (ret) ++ dev_err(dev, "get phy_use_sel is fail, %d\n", ret); ++ ++ combphy->phyclk_ref = devm_clk_get(&pdev->dev, "phyclk_ref"); ++ if (IS_ERR(combphy->phyclk_ref)) ++ dev_dbg(dev, "failed to get phyclk_ref\n"); ++ ++ combphy->reset = devm_reset_control_get(dev, "phy_rst"); ++ if (IS_ERR(combphy->reset)) { ++ dev_err(dev, "failed to get reset control\n"); ++ return PTR_ERR(combphy->reset); ++ } ++ ++ if (combphy->drvdata->need_noppu_rst) { ++ combphy->noppu_reset = devm_reset_control_get(dev, "noppu_rst"); ++ if (IS_ERR(combphy->noppu_reset)) { ++ dev_err(dev, "failed to get noppu_reset control\n"); ++ return PTR_ERR(combphy->noppu_reset); ++ } ++ } ++ if (combphy->user == PHY_USE_BY_PCIE || combphy->user == PHY_USE_BY_PCIE_USB3_U2) { ++ ++ combphy->refclk_par = devm_clk_get(&pdev->dev, "refclk_par"); ++ if (IS_ERR(combphy->refclk_par)) ++ dev_dbg(dev, "failed to get refclk_par\n"); ++ ++ if (IS_ERR(combphy->phyclk_ref) || IS_ERR(combphy->refclk_par)) { ++ dev_err(dev, "failed to get required clocks for ref\n"); ++ return -EINVAL; ++ } ++ ++ ret = clk_set_parent(combphy->phyclk_ref, combphy->refclk_par); ++ if (ret) { ++ dev_err(dev, "failed to set refclk parent\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (combphy->drvdata->has_cfg_clk) { ++ combphy->phyclk_cfg = devm_clk_get(&pdev->dev, "phyclk_cfg"); ++ if (IS_ERR(combphy->phyclk_cfg)) ++ dev_dbg(dev, "failed to get phyclk_cfg\n"); ++ ++ combphy->cfgclk_par = devm_clk_get(&pdev->dev, "cfgclk_par"); ++ if (IS_ERR(combphy->cfgclk_par)) ++ dev_dbg(dev, "failed to get cfgclk_par\n"); ++ ++ if (IS_ERR(combphy->phyclk_cfg) || IS_ERR(combphy->cfgclk_par)) { ++ dev_err(dev, "failed to get required clocks for cfg\n"); ++ return -EINVAL; ++ } ++ ++ ret = clk_set_parent(combphy->phyclk_cfg, combphy->cfgclk_par); ++ if (ret) { ++ dev_err(dev, "failed to set cfgclk parent\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (combphy->drvdata->has_pcie_axi_clk) { ++ combphy->phy_axi = devm_clk_get(&pdev->dev, "pclk_axi"); ++ if (IS_ERR(combphy->phy_axi)) { ++ dev_err(dev, "failed to get pclk_axi\n"); ++ return PTR_ERR(combphy->phy_axi); ++ } ++ ++ combphy->phy_axi_par = devm_clk_get(&pdev->dev, "pclk_axi_par"); ++ if (IS_ERR(combphy->phy_axi_par)) { ++ dev_err(dev, "failed to get pcie_axi_par\n"); ++ return PTR_ERR(combphy->phy_axi_par); ++ } ++ ++ ret = clk_set_parent(combphy->phy_axi, combphy->phy_axi_par); ++ if (ret) { ++ dev_err(dev, "failed to set parent\n"); ++ return -EINVAL; ++ } ++ } ++ ++ if (combphy->drvdata->has_phy_mbus_clk) { ++ combphy->phy_mclk = devm_clk_get(&pdev->dev, "phy_mclk"); ++ if (IS_ERR(combphy->phy_mclk)) { ++ dev_err(dev, "fail to get phy_mclk\n"); ++ return PTR_ERR(combphy->phy_mclk); ++ } ++ } ++ ++ if (combphy->drvdata->has_phy_ahb_clk) { ++ combphy->phy_hclk = devm_clk_get(&pdev->dev, "phy_hclk"); ++ if (IS_ERR(combphy->phy_hclk)) { ++ dev_err(dev, "fail to get phy_hclk\n"); ++ return PTR_ERR(combphy->phy_hclk); ++ } ++ } ++ ++ res_ctl = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy-ctl"); ++ if (!res_ctl) { ++ dev_err(&pdev->dev, "get phy-ctl failed\n"); ++ return -ENODEV; ++ } ++ ++ combphy->phy_ctl = devm_ioremap_resource(&pdev->dev, res_ctl); ++ if (IS_ERR(combphy->phy_ctl)) { ++ dev_err(&pdev->dev, "ioremap phy-ctl failed\n"); ++ return PTR_ERR(combphy->phy_ctl); ++ } ++ ++ res_clk = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phy-clk"); ++ if (!res_clk) { ++ dev_err(&pdev->dev, "get phy-clk failed\n"); ++ return -ENODEV; ++ } ++ ++ combphy->phy_clk = devm_ioremap_resource(&pdev->dev, res_clk); ++ if (IS_ERR(combphy->phy_clk)) { ++ dev_err(&pdev->dev, "ioremap phy-clk failed\n"); ++ return PTR_ERR(combphy->phy_clk); ++ } ++ ++ /* combo phy refclk sel */ ++ ret = of_property_read_u32(np, KEY_PHY_REFCLK_SEL, &combphy->ref); ++ if (ret) ++ dev_err(dev, "get phy_refclk_sel is fail, %d\n", ret); ++ ++ /* select ic supply */ ++ combphy->select3v3_supply = devm_regulator_get_optional(&pdev->dev, "select3v3"); ++ if (IS_ERR(combphy->select3v3_supply)) { ++ if (PTR_ERR(combphy->select3v3_supply) == -EPROBE_DEFER) ++ return PTR_ERR(combphy->select3v3_supply); ++ combphy->select3v3_supply = NULL; ++ } ++ ++ return 0; ++} ++ ++static int sunxi_combphy_probe(struct platform_device *pdev) ++{ ++ struct phy_provider *phy_provider; ++ struct device *dev = &pdev->dev; ++ struct sunxi_combphy *combphy; ++ const struct sunxi_combophy_of_data *data; ++ int ret; ++ ++ data = of_device_get_match_data(&pdev->dev); ++ ++ combphy = devm_kzalloc(dev, sizeof(*combphy), GFP_KERNEL); ++ if (!combphy) ++ return -ENOMEM; ++ ++ combphy->dev = dev; ++ combphy->mode = PHY_NONE; ++ combphy->drvdata = data; ++ ++ ret = sunxi_combphy_parse_dt(pdev, combphy); ++ if (ret) { ++ dev_err(dev, "failed to parse dts of combphy\n"); ++ return ret; ++ } ++ ++ combphy->phy = devm_phy_create(dev, NULL, &sunxi_combphy_ops); ++ if (IS_ERR(combphy->phy)) { ++ dev_err(dev, "failed to create combphy\n"); ++ return PTR_ERR(combphy->phy); ++ } ++ ++ platform_set_drvdata(pdev, combphy); ++ ++ ret = pcie_usb3_sub_system_init(pdev); ++ if (ret) { ++ dev_err(dev, "failed to init sub system\n"); ++ return ret; ++ } ++ ++ dev_info(dev, "Sub System Version: 0x%x\n", combphy->vernum); ++ ++ phy_set_drvdata(combphy->phy, combphy); ++ ++ phy_provider = devm_of_phy_provider_register(dev, sunxi_combphy_xlate); ++ ++ combphy->pwr_nb.notifier_call = sunxi_inno_combophy_power_event; ++ /* register inno power notifier */ ++ atomic_notifier_chain_register(&inno_subsys_notifier_list, &combphy->pwr_nb); ++ ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ pm_runtime_get_sync(dev); ++ ++ return PTR_ERR_OR_ZERO(phy_provider); ++} ++ ++static void sunxi_combphy_remove(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct sunxi_combphy *combphy = platform_get_drvdata(pdev); ++ int ret; ++ ++ ret = pcie_usb3_sub_system_exit(pdev); ++ if (ret) { ++ dev_err(dev, "failed to exit sub system\n"); ++ ++ } ++ ++ /* unregister inno power notifier */ ++ atomic_notifier_chain_unregister(&inno_subsys_notifier_list, &combphy->pwr_nb); ++ ++ pm_runtime_disable(dev); ++ pm_runtime_put_noidle(dev); ++ pm_runtime_set_suspended(dev); ++ ++ ++} ++ ++static int __maybe_unused sunxi_combo_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ int ret; ++ ++ ret = pcie_usb3_sub_system_exit(pdev); ++ ++ if (ret) { ++ dev_err(dev, "failed to suspend sub system\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int __maybe_unused sunxi_combo_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ int ret; ++ ++ ret = pcie_usb3_sub_system_init(pdev); ++ if (ret) { ++ dev_err(dev, "failed to resume sub system\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static struct dev_pm_ops sunxi_combo_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(sunxi_combo_suspend, sunxi_combo_resume) ++}; ++ ++/* ++ * inno-combphy: innosilicon combo phy ++ */ ++static const struct sunxi_combophy_of_data sunxi_inno_v1_of_data = { ++ .has_cfg_clk = false, ++}; ++ ++static const struct sunxi_combophy_of_data sunxi_inno_v2_of_data = { ++ .has_cfg_clk = true, ++ .has_slv_clk = true, ++ .has_phy_mbus_clk = true, ++ .has_phy_ahb_clk = true, ++ .has_pcie_axi_clk = true, ++ .has_u2_phy_mux = true, ++ .need_noppu_rst = true, ++ .has_u3_phy_data_quirk = true, ++ .need_optimize_jitter = true, ++}; ++ ++static const struct of_device_id sunxi_combphy_of_match[] = { ++ { ++ .compatible = "allwinner,inno-combphy", ++ .data = &sunxi_inno_v1_of_data, ++ }, ++ { ++ .compatible = "allwinner,inno-v2-combphy", ++ .data = &sunxi_inno_v2_of_data, ++ }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, sunxi_combphy_of_match); ++ ++static struct platform_driver sunxi_combphy_driver = { ++ .probe = sunxi_combphy_probe, ++ .remove = sunxi_combphy_remove, ++ .driver = { ++ .name = "inno-combphy", ++ .of_match_table = sunxi_combphy_of_match, ++ .pm = &sunxi_combo_pm_ops, ++ }, ++}; ++module_platform_driver(sunxi_combphy_driver); ++ ++MODULE_DESCRIPTION("Allwinner INNO COMBOPHY driver"); ++MODULE_AUTHOR("songjundong@allwinnertech.com"); ++MODULE_VERSION("0.0.20"); ++MODULE_LICENSE("GPL v2"); diff --git a/target/linux/sunxi/patches-6.18/126-pci-sunxi-enable-pcie-support.patch b/target/linux/sunxi/patches-6.18/126-pci-sunxi-enable-pcie-support.patch new file mode 100644 index 0000000000..c6e18fb74b --- /dev/null +++ b/target/linux/sunxi/patches-6.18/126-pci-sunxi-enable-pcie-support.patch @@ -0,0 +1,3236 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marvin Wewer +Date: Sun, 14 Dec 2025 11:07:45 +0000 +Subject: pci: sunxi: add sun55i PCIe RC and DMA support + +Signed-off-by: Marvin Wewer +--- + drivers/pci/Kconfig | 1 + + drivers/pci/Makefile | 1 + + drivers/pci/pcie-sunxi/Kconfig | 26 + + drivers/pci/pcie-sunxi/Makefile | 8 + + drivers/pci/pcie-sunxi/pcie-sunxi-dma.c | 198 ++ + drivers/pci/pcie-sunxi/pcie-sunxi-dma.h | 279 +++ + drivers/pci/pcie-sunxi/pcie-sunxi-plat.c | 1233 ++++++++++ + drivers/pci/pcie-sunxi/pcie-sunxi-rc.c | 854 +++++++ + drivers/pci/pcie-sunxi/pcie-sunxi.h | 391 +++ + include/sunxi-gpio.h | 188 ++ + 10 files changed, 3179 insertions(+) + +--- a/drivers/pci/Kconfig ++++ b/drivers/pci/Kconfig +@@ -328,5 +328,6 @@ source "drivers/pci/controller/Kconfig" + source "drivers/pci/endpoint/Kconfig" + source "drivers/pci/switch/Kconfig" + source "drivers/pci/pwrctrl/Kconfig" ++source "drivers/pci/pcie-sunxi/Kconfig" + + endif +--- a/drivers/pci/Makefile ++++ b/drivers/pci/Makefile +@@ -43,5 +43,6 @@ obj-$(CONFIG_PCI_ENDPOINT) += endpoint/ + + obj-y += controller/ + obj-y += switch/ ++obj-y += pcie-sunxi/ + + subdir-ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/Kconfig +@@ -0,0 +1,26 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++menu "PCIe Drivers" ++ depends on ARCH_SUNXI ++ ++choice ++ prompt "Allwinner PCIe controller" ++ default PCIE_SUN55I_NONE ++ ++config PCIE_SUN55I_RC ++ bool "Sun55i RC controller - Host mode" ++ depends on ARCH_SUNXI ++ help ++ Enables support for the PCIe RC controller in the Allwinner Sun55i SoC. ++ ++config PCIE_SUN55I_NONE ++ bool "None" ++ depends on ARCH_SUNXI ++ help ++ Disable support for the PCIe controller in the Allwinner Sun55i SoC. ++ ++endchoice ++ ++endmenu ++ ++ccflags-y += -Idrivers/pci/pcie-sunxi/include +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0 ++ccflag-y += -DDYNAMIC_DEBUG_MODULE ++ ++ccflags-y += -I $(srctree)/drivers/pci/ ++pcie_sunxi_host-objs := pcie-sunxi-rc.o pcie-sunxi-dma.o pcie-sunxi-plat.o ++pcie_sunxi_ep-objs := pcie-sunxi-ep.o pcie-sunxi-dma.o pcie-sunxi-plat.o ++obj-$(CONFIG_PCIE_SUN55I_RC) += pcie_sunxi_host.o +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/pcie-sunxi-dma.c +@@ -0,0 +1,198 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Copyright (C) 2022 Allwinner Co., Ltd. ++ * ++ * The pcie_dma_chnl_request() is used to apply for pcie DMA channels; ++ * The pcie_dma_mem_xxx() is to initiate DMA read and write operations; ++ * ++ */ ++ ++#define SUNXI_MODNAME "pcie-edma" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "pcie-sunxi-dma.h" ++ ++ ++static struct dma_trx_obj *obj_global; ++ ++sunxi_pci_edma_chan_t *sunxi_pcie_dma_chan_request(enum dma_dir dma_trx, void *cb, void *data) ++{ ++ struct sunxi_pcie *pci = dev_get_drvdata(obj_global->dev); ++ sunxi_pci_edma_chan_t *edma_chan = NULL; ++ u32 free_chan; ++ ++ if (dma_trx == PCIE_DMA_WRITE) { ++ free_chan = find_first_zero_bit(pci->wr_edma_map, pci->num_edma); ++ ++ if (free_chan >= pci->num_edma) { ++ dev_err(pci->dev, "No free pcie edma write channel.\n"); ++ return NULL; ++ } ++ ++ set_bit(free_chan, pci->wr_edma_map); ++ ++ edma_chan = &pci->dma_wr_chn[free_chan]; ++ ++ edma_chan->dma_trx = PCIE_DMA_WRITE; ++ edma_chan->chnl_num = free_chan; ++ edma_chan->callback = cb; ++ edma_chan->callback_param = data; ++ ++ return edma_chan; ++ } else if (dma_trx == PCIE_DMA_READ) { ++ free_chan = find_first_zero_bit(pci->rd_edma_map, pci->num_edma); ++ ++ if (free_chan >= pci->num_edma) { ++ dev_err(pci->dev, "No free pcie edma read channel.\n"); ++ return NULL; ++ } ++ ++ set_bit(free_chan, pci->rd_edma_map); ++ ++ edma_chan = &pci->dma_rd_chn[free_chan]; ++ ++ edma_chan->dma_trx = PCIE_DMA_READ; ++ edma_chan->chnl_num = free_chan; ++ edma_chan->callback = cb; ++ edma_chan->callback_param = data; ++ ++ return edma_chan; ++ } else { ++ dev_err(pci->dev, "ERR: unsupported type:%d \n", dma_trx); ++ } ++ ++ return NULL; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_dma_chan_request); ++ ++int sunxi_pcie_dma_chan_release(struct sunxi_pci_edma_chan *edma_chan, enum dma_dir dma_trx) ++{ ++ struct sunxi_pcie *pci = dev_get_drvdata(obj_global->dev); ++ ++ if (edma_chan->chnl_num >= pci->num_edma) { ++ dev_err(pci->dev, "ERR: the channel num:%d is error\n", edma_chan->chnl_num); ++ return -1; ++ } ++ ++ if (PCIE_DMA_WRITE == dma_trx) { ++ edma_chan->callback = NULL; ++ edma_chan->callback_param = NULL; ++ clear_bit(edma_chan->chnl_num, pci->wr_edma_map); ++ } else if (PCIE_DMA_READ == dma_trx) { ++ edma_chan->callback = NULL; ++ edma_chan->callback_param = NULL; ++ clear_bit(edma_chan->chnl_num, pci->rd_edma_map); ++ } else { ++ dev_err(pci->dev, "ERR: unsupported type:%d \n", dma_trx); ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_dma_chan_release); ++ ++static int sunxi_pcie_init_edma_map(struct sunxi_pcie *pci) ++{ ++ pci->rd_edma_map = devm_bitmap_zalloc(pci->dev, pci->num_edma, GFP_KERNEL); ++ if (!pci->rd_edma_map) ++ return -ENOMEM; ++ ++ pci->wr_edma_map = devm_bitmap_zalloc(pci->dev, pci->num_edma, GFP_KERNEL); ++ if (!pci->wr_edma_map) ++ return -ENOMEM; ++ ++ return 0; ++} ++ ++int sunxi_pcie_dma_get_chan(struct platform_device *pdev) ++{ ++ struct sunxi_pcie *pci = platform_get_drvdata(pdev); ++ sunxi_pci_edma_chan_t *edma_chan = NULL; ++ int ret, i; ++ ++ ret = of_property_read_u32(pdev->dev.of_node, "num-edma", &pci->num_edma); ++ if (ret) { ++ dev_err(&pdev->dev, "Failed to parse the number of edma\n"); ++ return -EINVAL; ++ } else { ++ ret = sunxi_pcie_init_edma_map(pci); ++ if (ret) ++ return -EINVAL; ++ } ++ ++ pci->dma_wr_chn = devm_kcalloc(&pdev->dev, pci->num_edma, sizeof(sunxi_pci_edma_chan_t), GFP_KERNEL); ++ pci->dma_rd_chn = devm_kcalloc(&pdev->dev, pci->num_edma, sizeof(sunxi_pci_edma_chan_t), GFP_KERNEL); ++ if (!pci->dma_wr_chn || !pci->dma_rd_chn) { ++ dev_err(&pdev->dev, "PCIe edma init failed\n"); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < pci->num_edma; i++) { ++ edma_chan = &pci->dma_wr_chn[i]; ++ spin_lock_init(&edma_chan->lock); ++ } ++ ++ for (i = 0; i < pci->num_edma; i++) { ++ edma_chan = &pci->dma_rd_chn[i]; ++ spin_lock_init(&edma_chan->lock); ++ } ++ ++ return 0; ++} ++ ++struct dma_trx_obj *sunxi_pcie_dma_obj_probe(struct device *dev) ++{ ++ struct dma_trx_obj *obj; ++ ++ obj = devm_kzalloc(dev, sizeof(*obj), GFP_KERNEL); ++ if (!obj) ++ return ERR_PTR(-ENOMEM); ++ ++ obj_global = obj; ++ obj->dev = dev; ++ ++ INIT_LIST_HEAD(&obj->dma_list); ++ spin_lock_init(&obj->dma_list_lock); ++ ++ mutex_init(&obj->count_mutex); ++ ++ return obj; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_dma_obj_probe); ++ ++int sunxi_pcie_dma_obj_remove(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_pcie *pci = platform_get_drvdata(pdev); ++ ++ memset(pci->dma_wr_chn, 0, sizeof(sunxi_pci_edma_chan_t) * pci->num_edma); ++ memset(pci->dma_rd_chn, 0, sizeof(sunxi_pci_edma_chan_t) * pci->num_edma); ++ ++ obj_global->dma_list.next = NULL; ++ obj_global->dma_list.prev = NULL; ++ mutex_destroy(&obj_global->count_mutex); ++ ++ obj_global = NULL; ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_dma_obj_remove); +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/pcie-sunxi-dma.h +@@ -0,0 +1,278 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * allwinner PCIe dma driver ++ * ++ * Copyright (C) 2022 allwinner Co., Ltd. ++ * ++ * Author: songjundong ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++#ifndef _PCIE_SUNXI_DMA_H ++#define _PCIE_SUNXI_DMA_H ++ ++#include ++#include ++ ++#include "pcie-sunxi.h" ++ ++#define PCIE_DMA_TABLE_NUM 8 ++#define PCIE_DMA_TRX_TYPE_NUM 3 ++ ++#define PCIE_WEIGHT 0x1f ++/* ++ * MASK_DONE_CNT_xx and MASK_ABORT_CNT_xx used in dma interrupt ++ */ ++#define MASK_DONE_CNT_WR ((2 << (PCIE_DMA_WR_CHN_CNT - 1)) - 1) ++#define MASK_DONE_CNT_RD ((2 << (PCIE_DMA_RD_CHN_CNT - 1)) - 1) ++ ++#define MASK_ABORD_CNT_WR (((2 << (PCIE_DMA_WR_CHN_CNT - 1)) - 1)) ++#define MASK_ABORD_CNT_RD (((2 << (PCIE_DMA_RD_CHN_CNT - 1)) - 1)) ++ ++#define PCIE_DMA_OFFSET 0x380000 ++ ++#define PCIE_DMA_WR_ENB 0xc ++#define PCIE_DMA_WR_CTRL_LO 0x200 ++#define PCIE_DMA_WR_CTRL_HI 0x204 ++#define PCIE_DMA_WR_XFERSIZE 0x208 ++#define PCIE_DMA_WR_SAR_LO 0x20c ++#define PCIE_DMA_WR_SAR_HI 0x210 ++#define PCIE_DMA_WR_DAR_LO 0x214 ++#define PCIE_DMA_WR_DAR_HI 0x218 ++#define PCIE_DMA_WR_WEILO 0x18 ++#define PCIE_DMA_WR_WEIHI 0x1c ++#define PCIE_DMA_WR_DOORBELL 0x10 ++#define PCIE_DMA_WR_INT_STATUS 0x4c ++#define PCIE_DMA_WR_INT_MASK 0x54 ++#define PCIE_DMA_WR_INT_CLEAR 0x58 ++ ++#define PCIE_DMA_RD_ENB 0x2c ++#define PCIE_DMA_RD_CTRL_LO 0x300 ++#define PCIE_DMA_RD_CTRL_HI 0x304 ++#define PCIE_DMA_RD_XFERSIZE 0x308 ++#define PCIE_DMA_RD_SAR_LO 0x30c ++#define PCIE_DMA_RD_SAR_HI 0x310 ++#define PCIE_DMA_RD_DAR_LO 0x314 ++#define PCIE_DMA_RD_DAR_HI 0x318 ++#define PCIE_DMA_RD_WEILO 0x38 ++#define PCIE_DMA_RD_WEIHI 0x3c ++#define PCIE_DMA_RD_DOORBELL 0x30 ++#define PCIE_DMA_RD_INT_STATUS 0xa0 ++#define PCIE_DMA_RD_INT_MASK 0xa8 ++#define PCIE_DMA_RD_INT_CLEAR 0xac ++ ++#define PCIE_DMA_INT_MASK 0xf000f ++ ++enum dma_dir { ++ PCIE_DMA_WRITE = 0, ++ PCIE_DMA_READ, ++}; ++ ++typedef void (*sunxi_pcie_edma_callback)(void *param); ++ ++typedef struct sunxi_pci_edma_chan { ++ u32 chnl_num; ++ spinlock_t lock; ++ bool cookie; ++ phys_addr_t src_addr; ++ phys_addr_t dst_addr; ++ u32 size; ++ enum dma_dir dma_trx; ++ void *callback_param; ++ sunxi_pcie_edma_callback callback; ++} sunxi_pci_edma_chan_t; ++ ++/* ++ * The Channel Control Register for read and write. ++ */ ++union chan_ctrl_lo { ++ struct { ++ u32 cb :1; /* 0 bit */ ++ u32 tcb :1; /* 1 */ ++ u32 llp :1; /* 2 */ ++ u32 lie :1; /* 3 */ ++ u32 rie :1; /* 4 */ ++ u32 cs :2; /* 5:6 */ ++ u32 rsvd1 :1; /* 7 */ ++ u32 ccs :1; /* 8 */ ++ u32 llen :1; /* 9 */ ++ u32 b_64s :1; /* 10 */ ++ u32 b_64d :1; /* 11 */ ++ u32 fn :5; /* 12:16 */ ++ u32 rsvd2 :7; /* 17:23 */ ++ u32 ns :1; /* 24 */ ++ u32 ro :1; /* 25 */ ++ u32 td :1; /* 26 */ ++ u32 tc :3; /* 27:29 */ ++ u32 at :2; /* 30:31 */ ++ }; ++ u32 dword; ++}; ++ ++/* ++ * The Channel Control Register high part for read and write. ++ * Note: depend on CX_SRIOV_ENABLE ++ * Note: Need to confirm the difference between PCIe 2.0 with 3.0 ++ */ ++union chan_ctrl_hi { ++ struct { ++ u32 vfenb :1; /* 0 bit */ ++ u32 vfunc :8; /* 1-8 */ ++ u32 rsvd0 :23; /* 9-31 */ ++ }; ++ u32 dword; ++}; ++ ++struct ctx_reg { ++ union chan_ctrl_lo ctrllo; ++ union chan_ctrl_hi ctrlhi; ++ u32 xfersize; ++ u32 sarptrlo; ++ u32 sarptrhi; ++ u32 darptrlo; ++ u32 darptrhi; ++}; ++ ++/* ++ * The Channel Weight Register for read and write. ++ * ++ * weight_lo->weight0 means set channel 0 ++ * weight_hi->weight0 means set channel 4; ++ * ++ * Example: ++ * write channel #0 weight to 32 ++ * write channel #1 weight to 16 ++ * ++ * Then the DMA will issue 32 MRd requests for #0,followed by 16 MRd requests for #1, ++ * followed by the 32 MRd requests for #0 and so on... ++ */ ++union weight { ++ struct { ++ u32 weight0 :5; /* 0:4 bit */ ++ u32 weight1 :5; /* 5:9 */ ++ u32 weight2 :5; /* 10:14 */ ++ u32 weight3 :5; /* 15:19 */ ++ u32 rsvd :12; /* 20:31 */ ++ }; ++ u32 dword; ++}; ++ ++ ++/* ++ * The Doorbell Register for read and write. ++ * if is read db: you need write 0x0 for that channel ++ * if is write db: you need write channel number for that channel. ++ */ ++union db { ++ struct { ++ u32 chnl :3; /* 0 bit */ ++ u32 rsvd :28; /* 3:30 */ ++ u32 stop :1; /* 31 */ ++ }; ++ u32 dword; ++}; ++ ++/* ++ * The Enable VIEWPORT Register for read and write. ++ */ ++union enb { ++ struct { ++ u32 enb :1; /* 0 bit */ ++ u32 rsvd :31; /* 1:31 */ ++ }; ++ u32 dword; ++}; ++ ++/* ++ * The Interrupt Status Register for read and write. ++ */ ++union int_status { ++ struct { ++ u32 done :8; /* 0:7 bit */ ++ u32 rsvd0 :8; /* 8:15 */ ++ u32 abort :8; /* 16:23 */ ++ u32 rsvd1 :8; /* 24:31 */ ++ }; ++ u32 dword; ++}; ++ ++/* ++ * The Interrupt Status Register for read and write. ++ */ ++union int_clear { ++ struct { ++ u32 doneclr :8; /* 0:7 bit */ ++ u32 rsvd0 :8; /* 8:15 */ ++ u32 abortclr :8; /* 16:23 */ ++ u32 rsvd1 :8; /* 24:31 */ ++ }; ++ u32 dword; ++}; ++ ++/* ++ * The Context Registers for read and write. ++ */ ++struct ctx_regs { ++ union chan_ctrl_lo ctrllo; ++ union chan_ctrl_hi ctrlhi; ++ u32 xfersize; ++ u32 sarptrlo; ++ u32 sarptrhi; ++ u32 darptrlo; ++ u32 darptrhi; ++}; ++ ++struct dma_table { ++ u32 *descs; ++ int chn; ++ phys_addr_t phys_descs; ++ enum dma_dir dir; ++ u32 type; ++ struct list_head dma_tbl; ++ union enb enb; ++ struct ctx_regs ctx_reg; ++ union weight weilo; ++ union weight weihi; ++ union db start; ++ phys_addr_t local; ++ phys_addr_t bus; ++ size_t size; ++}; ++ ++struct dma_trx_obj { ++ struct device *dev; ++ void *mem_base; ++ phys_addr_t mem_start; ++ size_t mem_size; ++ int dma_free; ++ spinlock_t dma_list_lock; /* lock dma table */ ++ struct list_head dma_list; ++ struct work_struct dma_trx_work; ++ wait_queue_head_t event_queue; ++ struct workqueue_struct *dma_trx_wq; ++ struct dma_table *table[PCIE_DMA_TABLE_NUM]; ++ struct task_struct *scan_thread; ++ struct hrtimer scan_timer; ++ void *priv; ++ struct completion done; ++ int ref_count; ++ struct mutex count_mutex; ++ unsigned long irq_num; ++ struct dentry *pcie_root; ++ struct pcie_misc_dev *pcie_dev; ++ void (*start_dma_trx_func)(struct dma_table *table, struct dma_trx_obj *obj); ++ int (*config_dma_trx_func)(struct dma_table *table, phys_addr_t sar_addr, phys_addr_t dar_addr, ++ unsigned int size, enum dma_dir dma_trx, sunxi_pci_edma_chan_t *edma_chn); ++}; ++ ++struct dma_trx_obj *sunxi_pcie_dma_obj_probe(struct device *dev); ++int sunxi_pcie_dma_obj_remove(struct device *dev); ++sunxi_pci_edma_chan_t *sunxi_pcie_dma_chan_request(enum dma_dir dma_trx, void *cb, void *data); ++int sunxi_pcie_dma_chan_release(struct sunxi_pci_edma_chan *edma_chan, enum dma_dir dma_trx); ++ ++int sunxi_pcie_dma_get_chan(struct platform_device *pdev); ++ ++#endif +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/pcie-sunxi-plat.c +@@ -0,0 +1,1233 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * PCIe driver for Allwinner Soc ++ * ++ * Copyright (C) 2022 Allwinner Co., Ltd. ++ * ++ * Author: songjundong ++ * ++ * 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. ++ */ ++ ++#define SUNXI_MODNAME "pcie" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pci.h" ++#include "pcie-sunxi-dma.h" ++#include "pcie-sunxi.h" ++ ++#define SUNXI_PCIE_MODULE_VERSION "1.2.4" ++ ++void sunxi_pcie_writel(u32 val, struct sunxi_pcie *pcie, u32 offset) ++{ ++ writel(val, pcie->app_base + offset); ++} ++ ++u32 sunxi_pcie_readl(struct sunxi_pcie *pcie, u32 offset) ++{ ++ return readl(pcie->app_base + offset); ++} ++ ++void sunxi_pcie_writel_dbi(struct sunxi_pcie *pci, u32 reg, u32 val) ++{ ++ sunxi_pcie_write_dbi(pci, reg, 0x4, val); ++} ++ ++u32 sunxi_pcie_readl_dbi(struct sunxi_pcie *pci, u32 reg) ++{ ++ return sunxi_pcie_read_dbi(pci, reg, 0x4); ++} ++ ++void sunxi_pcie_writew_dbi(struct sunxi_pcie *pci, u32 reg, u16 val) ++{ ++ sunxi_pcie_write_dbi(pci, reg, 0x2, val); ++} ++ ++u16 sunxi_pcie_readw_dbi(struct sunxi_pcie *pci, u32 reg) ++{ ++ return sunxi_pcie_read_dbi(pci, reg, 0x2); ++} ++ ++void sunxi_pcie_writeb_dbi(struct sunxi_pcie *pci, u32 reg, u8 val) ++{ ++ sunxi_pcie_write_dbi(pci, reg, 0x1, val); ++} ++ ++u8 sunxi_pcie_readb_dbi(struct sunxi_pcie *pci, u32 reg) ++{ ++ return sunxi_pcie_read_dbi(pci, reg, 0x1); ++} ++ ++void sunxi_pcie_dbi_ro_wr_en(struct sunxi_pcie *pci) ++{ ++ u32 val; ++ ++ val = sunxi_pcie_readl_dbi(pci, PCIE_MISC_CONTROL_1_CFG); ++ val |= (0x1 << 0); ++ sunxi_pcie_writel_dbi(pci, PCIE_MISC_CONTROL_1_CFG, val); ++} ++ ++void sunxi_pcie_dbi_ro_wr_dis(struct sunxi_pcie *pci) ++{ ++ u32 val; ++ ++ val = sunxi_pcie_readl_dbi(pci, PCIE_MISC_CONTROL_1_CFG); ++ val &= ~(0x1 << 0); ++ sunxi_pcie_writel_dbi(pci, PCIE_MISC_CONTROL_1_CFG, val); ++} ++ ++static void sunxi_pcie_plat_set_mode(struct sunxi_pcie *pci) ++{ ++ u32 val; ++ ++ switch (pci->drvdata->mode) { ++ case SUNXI_PCIE_EP_TYPE: ++ val = sunxi_pcie_readl(pci, PCIE_LTSSM_CTRL); ++ val &= ~DEVICE_TYPE_MASK; ++ sunxi_pcie_writel(val, pci, PCIE_LTSSM_CTRL); ++ break; ++ case SUNXI_PCIE_RC_TYPE: ++ val = sunxi_pcie_readl(pci, PCIE_LTSSM_CTRL); ++ val |= DEVICE_TYPE_RC; ++ sunxi_pcie_writel(val, pci, PCIE_LTSSM_CTRL); ++ break; ++ default: ++ dev_err(pci->dev, "unsupported device type:%d\n", pci->drvdata->mode); ++ break; ++ } ++} ++ ++static u8 __sunxi_pcie_find_next_cap(struct sunxi_pcie *pci, u8 cap_ptr, ++ u8 cap) ++{ ++ u8 cap_id, next_cap_ptr; ++ u16 reg; ++ ++ if (!cap_ptr) ++ return 0; ++ ++ reg = sunxi_pcie_readw_dbi(pci, cap_ptr); ++ cap_id = (reg & CAP_ID_MASK); ++ ++ if (cap_id > PCI_CAP_ID_MAX) ++ return 0; ++ ++ if (cap_id == cap) ++ return cap_ptr; ++ ++ next_cap_ptr = (reg & NEXT_CAP_PTR_MASK) >> 8; ++ return __sunxi_pcie_find_next_cap(pci, next_cap_ptr, cap); ++} ++ ++u8 sunxi_pcie_plat_find_capability(struct sunxi_pcie *pci, u8 cap) ++{ ++ u8 next_cap_ptr; ++ u16 reg; ++ ++ reg = sunxi_pcie_readw_dbi(pci, PCI_CAPABILITY_LIST); ++ next_cap_ptr = (reg & CAP_ID_MASK); ++ ++ return __sunxi_pcie_find_next_cap(pci, next_cap_ptr, cap); ++} ++ ++int sunxi_pcie_cfg_read(void __iomem *addr, int size, u32 *val) ++{ ++ if ((uintptr_t)addr & (size - 1)) { ++ *val = 0; ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ } ++ ++ if (size == 4) { ++ *val = readl(addr); ++ } else if (size == 2) { ++ *val = readw(addr); ++ } else if (size == 1) { ++ *val = readb(addr); ++ } else { ++ *val = 0; ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ } ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_cfg_read); ++ ++int sunxi_pcie_cfg_write(void __iomem *addr, int size, u32 val) ++{ ++ if ((uintptr_t)addr & (size - 1)) ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ ++ if (size == 4) ++ writel(val, addr); ++ else if (size == 2) ++ writew(val, addr); ++ else if (size == 1) ++ writeb(val, addr); ++ else ++ return PCIBIOS_BAD_REGISTER_NUMBER; ++ ++ return PCIBIOS_SUCCESSFUL; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_cfg_write); ++ ++void sunxi_pcie_write_dbi(struct sunxi_pcie *pci, u32 reg, size_t size, u32 val) ++{ ++ int ret; ++ ++ ret = sunxi_pcie_cfg_write(pci->dbi_base + reg, size, val); ++ if (ret) ++ dev_err(pci->dev, "Write DBI address failed\n"); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_write_dbi); ++ ++u32 sunxi_pcie_read_dbi(struct sunxi_pcie *pci, u32 reg, size_t size) ++{ ++ int ret; ++ u32 val; ++ ++ ret = sunxi_pcie_cfg_read(pci->dbi_base + reg, size, &val); ++ if (ret) ++ dev_err(pci->dev, "Read DBI address failed\n"); ++ ++ return val; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_read_dbi); ++ ++static void sunxi_pcie_plat_set_link_cap(struct sunxi_pcie *pci, u32 link_gen) ++{ ++ u32 cap, ctrl2, link_speed; ++ ++ u8 offset = sunxi_pcie_plat_find_capability(pci, PCI_CAP_ID_EXP); ++ ++ cap = sunxi_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCAP); ++ ctrl2 = sunxi_pcie_readl_dbi(pci, offset + PCI_EXP_LNKCTL2); ++ ctrl2 &= ~PCI_EXP_LNKCTL2_TLS; ++ ++ switch (pcie_link_speed[link_gen]) { ++ case PCIE_SPEED_2_5GT: ++ link_speed = PCI_EXP_LNKCTL2_TLS_2_5GT; ++ break; ++ case PCIE_SPEED_5_0GT: ++ link_speed = PCI_EXP_LNKCTL2_TLS_5_0GT; ++ break; ++ case PCIE_SPEED_8_0GT: ++ link_speed = PCI_EXP_LNKCTL2_TLS_8_0GT; ++ break; ++ case PCIE_SPEED_16_0GT: ++ link_speed = PCI_EXP_LNKCTL2_TLS_16_0GT; ++ break; ++ default: ++ /* Use hardware capability */ ++ link_speed = FIELD_GET(PCI_EXP_LNKCAP_SLS, cap); ++ ctrl2 &= ~PCI_EXP_LNKCTL2_HASD; ++ break; ++ } ++ ++ sunxi_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCTL2, ctrl2 | link_speed); ++ ++ cap &= ~((u32)PCI_EXP_LNKCAP_SLS); ++ sunxi_pcie_writel_dbi(pci, offset + PCI_EXP_LNKCAP, cap | link_speed); ++} ++ ++void sunxi_pcie_plat_set_rate(struct sunxi_pcie *pci) ++{ ++ u32 val; ++ ++ sunxi_pcie_plat_set_link_cap(pci, pci->link_gen); ++ /* set the number of lanes */ ++ val = sunxi_pcie_readl_dbi(pci, PCIE_PORT_LINK_CONTROL); ++ val &= ~PORT_LINK_MODE_MASK; ++ switch (pci->lanes) { ++ case 1: ++ val |= PORT_LINK_MODE_1_LANES; ++ break; ++ case 2: ++ val |= PORT_LINK_MODE_2_LANES; ++ break; ++ case 4: ++ val |= PORT_LINK_MODE_4_LANES; ++ break; ++ default: ++ dev_err(pci->dev, "num-lanes %u: invalid value\n", pci->lanes); ++ return; ++ } ++ sunxi_pcie_writel_dbi(pci, PCIE_PORT_LINK_CONTROL, val); ++ ++ /* set link width speed control register */ ++ val = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); ++ val &= ~PORT_LOGIC_LINK_WIDTH_MASK; ++ switch (pci->lanes) { ++ case 1: ++ val |= PORT_LOGIC_LINK_WIDTH_1_LANES; ++ break; ++ case 2: ++ val |= PORT_LOGIC_LINK_WIDTH_2_LANES; ++ break; ++ case 4: ++ val |= PORT_LOGIC_LINK_WIDTH_4_LANES; ++ break; ++ } ++ sunxi_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_plat_set_rate); ++ ++static unsigned int sunxi_pcie_ep_func_conf_select(struct sunxi_pcie_ep *ep, ++ u8 func_no) ++{ ++ struct sunxi_pcie *pcie = to_sunxi_pcie_from_ep(ep); ++ ++ WARN_ON(func_no && !pcie->drvdata->func_offset); ++ return pcie->drvdata->func_offset * func_no; ++} ++ ++static const struct sunxi_pcie_ep_ops sunxi_ep_ops = { ++ .func_conf_select = sunxi_pcie_ep_func_conf_select, ++}; ++ ++static const struct sunxi_pcie_of_data sunxi_pcie_rc_v210_of_data = { ++ .mode = SUNXI_PCIE_RC_TYPE, ++ .cpu_pcie_addr_quirk = true, ++}; ++ ++static const struct sunxi_pcie_of_data sunxi_pcie_rc_v210_v2_of_data = { ++ .mode = SUNXI_PCIE_RC_TYPE, ++ .has_pcie_slv_clk = true, ++ .need_pcie_rst = true, ++}; ++ ++static const struct sunxi_pcie_of_data sunxi_pcie_rc_v210_v3_of_data = { ++ .mode = SUNXI_PCIE_RC_TYPE, ++ .has_pcie_slv_clk = true, ++ .need_pcie_rst = true, ++}; ++ ++static const struct sunxi_pcie_of_data sunxi_pcie_rc_v300_of_data = { ++ .mode = SUNXI_PCIE_RC_TYPE, ++ .has_pcie_slv_clk = true, ++ .need_pcie_rst = true, ++ .pcie_slv_clk_400m = true, ++ .has_pcie_its_clk = true, ++}; ++ ++static const struct sunxi_pcie_of_data sunxi_pcie_ep_v210_of_data = { ++ .mode = SUNXI_PCIE_EP_TYPE, ++ .func_offset = 0x10000, ++ .ops = &sunxi_ep_ops, ++ .has_pcie_slv_clk = true, ++ .need_pcie_rst = true, ++}; ++ ++static const struct sunxi_pcie_of_data sunxi_pcie_ep_v300_of_data = { ++ .mode = SUNXI_PCIE_EP_TYPE, ++ .func_offset = 0x10000, ++ .ops = &sunxi_ep_ops, ++}; ++ ++static const struct of_device_id sunxi_pcie_plat_of_match[] = { ++ { ++ .compatible = "allwinner,sunxi-pcie-v210-rc", ++ .data = &sunxi_pcie_rc_v210_of_data, ++ }, ++ { ++ .compatible = "allwinner,sunxi-pcie-v210-v2-rc", ++ .data = &sunxi_pcie_rc_v210_v2_of_data, ++ }, ++ { ++ .compatible = "allwinner,sunxi-pcie-v210-v3-rc", ++ .data = &sunxi_pcie_rc_v210_v3_of_data, ++ }, ++ { ++ .compatible = "allwinner,sunxi-pcie-v210-ep", ++ .data = &sunxi_pcie_ep_v210_of_data, ++ }, ++ { ++ .compatible = "allwinner,sunxi-pcie-v300-rc", ++ .data = &sunxi_pcie_rc_v300_of_data, ++ }, ++ { ++ .compatible = "allwinner,sunxi-pcie-v300-ep", ++ .data = &sunxi_pcie_ep_v300_of_data, ++ }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, sunxi_pcie_plat_of_match); ++ ++void sunxi_pcie_plat_ltssm_enable(struct sunxi_pcie *pcie) ++{ ++ u32 val; ++ ++ val = sunxi_pcie_readl(pcie, PCIE_LTSSM_CTRL); ++ val |= PCIE_LINK_TRAINING; ++ sunxi_pcie_writel(val, pcie, PCIE_LTSSM_CTRL); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_plat_ltssm_enable); ++ ++void sunxi_pcie_plat_ltssm_disable(struct sunxi_pcie *pcie) ++{ ++ u32 val; ++ ++ val = sunxi_pcie_readl(pcie, PCIE_LTSSM_CTRL); ++ val &= ~PCIE_LINK_TRAINING; ++ sunxi_pcie_writel(val, pcie, PCIE_LTSSM_CTRL); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_plat_ltssm_disable); ++ ++static void sunxi_pcie_plat_irqpending(struct sunxi_pcie_port *pp) ++{ ++ struct sunxi_pcie *pcie = to_sunxi_pcie_from_pp(pp); ++ u32 val; ++ ++ val = sunxi_pcie_readl(pcie, PCIE_INT_ENABLE_CLR); ++ val &= ~PCIE_LINK_INT_EN; ++ sunxi_pcie_writel(val, pcie, PCIE_INT_ENABLE_CLR); ++} ++ ++static void sunxi_pcie_plat_set_irqmask(struct sunxi_pcie *pci) ++{ ++ u32 val; ++ ++ val = sunxi_pcie_readl(pci, PCIE_INT_ENABLE_CLR); ++ val |= PCIE_LINK_INT_EN; ++ sunxi_pcie_writel(val, pci, PCIE_INT_ENABLE_CLR); ++} ++ ++static int sunxi_pcie_plat_power_on(struct sunxi_pcie *pci) ++{ ++ struct device *dev = pci->dev; ++ int ret = 0; ++ ++ if (!IS_ERR(pci->pcie3v3)) { ++ ret = regulator_enable(pci->pcie3v3); ++ if (ret) ++ dev_err(dev, "failed to enable pcie3v3 regulator\n"); ++ } ++ ++ return ret; ++} ++ ++static void sunxi_pcie_plat_power_off(struct sunxi_pcie *pci) ++{ ++ if (!IS_ERR(pci->pcie3v3)) ++ regulator_disable(pci->pcie3v3); ++} ++ ++static int sunxi_pcie_plat_clk_setup(struct sunxi_pcie *pci) ++{ ++ int ret; ++ ++ if (pci->drvdata->need_pcie_rst) { ++ ret = reset_control_deassert(pci->pcie_rst); ++ if (ret) { ++ dev_err(pci->dev, "cannot reset pcie\n"); ++ return ret; ++ } ++ ++ ret = reset_control_deassert(pci->pwrup_rst); ++ if (ret) { ++ dev_err(pci->dev, "cannot pwrup_reset pcie\n"); ++ goto err0; ++ } ++ } ++ ++ ret = clk_prepare_enable(pci->pcie_aux); ++ if (ret) { ++ dev_err(pci->dev, "cannot prepare/enable aux clock\n"); ++ goto err1; ++ } ++ ++ if (pci->drvdata->has_pcie_slv_clk) { ++ if (pci->drvdata->pcie_slv_clk_400m) { ++ ret = clk_set_rate(pci->pcie_slv, 400000000); ++ if (ret) { ++ dev_err(pci->dev, "cannot set slv clock\n"); ++ goto err2; ++ } ++ } ++ ret = clk_prepare_enable(pci->pcie_slv); ++ if (ret) { ++ dev_err(pci->dev, "cannot prepare/enable slv clock\n"); ++ goto err2; ++ } ++ } ++ ++ if (pci->drvdata->has_pcie_its_clk) { ++ ret = reset_control_deassert(pci->pcie_its_rst); ++ if (ret) { ++ dev_err(pci->dev, "cannot reset pcie its\n"); ++ goto err3; ++ } ++ ++ ret = clk_prepare_enable(pci->pcie_its); ++ if (ret) { ++ dev_err(pci->dev, "cannot prepare/enable its clock\n"); ++ goto err4; ++ } ++ } ++ ++ return 0; ++err4: ++ if (pci->drvdata->has_pcie_its_clk) ++ reset_control_assert(pci->pcie_its_rst); ++err3: ++ if (pci->drvdata->has_pcie_slv_clk) ++ clk_disable_unprepare(pci->pcie_slv); ++err2: ++ clk_disable_unprepare(pci->pcie_aux); ++err1: ++ if (pci->drvdata->need_pcie_rst) ++ reset_control_assert(pci->pwrup_rst); ++err0: ++ if (pci->drvdata->need_pcie_rst) ++ reset_control_assert(pci->pcie_rst); ++ ++ return ret; ++} ++ ++static void sunxi_pcie_plat_clk_exit(struct sunxi_pcie *pci) ++{ ++ if (pci->drvdata->has_pcie_its_clk) { ++ clk_disable_unprepare(pci->pcie_its); ++ reset_control_assert(pci->pcie_its_rst); ++ } ++ ++ if (pci->drvdata->has_pcie_slv_clk) ++ clk_disable_unprepare(pci->pcie_slv); ++ ++ clk_disable_unprepare(pci->pcie_aux); ++ ++ if (pci->drvdata->need_pcie_rst) { ++ reset_control_assert(pci->pcie_rst); ++ reset_control_assert(pci->pwrup_rst); ++ } ++} ++ ++static int sunxi_pcie_plat_clk_get(struct platform_device *pdev, struct sunxi_pcie *pci) ++{ ++ pci->pcie_aux = devm_clk_get(&pdev->dev, "pclk_aux"); ++ if (IS_ERR(pci->pcie_aux)) { ++ dev_err(&pdev->dev, "fail to get pclk_aux\n"); ++ return PTR_ERR(pci->pcie_aux); ++ } ++ ++ if (pci->drvdata->has_pcie_slv_clk) { ++ pci->pcie_slv = devm_clk_get(&pdev->dev, "pclk_slv"); ++ if (IS_ERR(pci->pcie_slv)) { ++ dev_err(&pdev->dev, "fail to get pclk_slv\n"); ++ return PTR_ERR(pci->pcie_slv); ++ } ++ } ++ ++ if (pci->drvdata->need_pcie_rst) { ++ pci->pcie_rst = devm_reset_control_get(&pdev->dev, "pclk_rst"); ++ if (IS_ERR(pci->pcie_rst)) { ++ dev_err(&pdev->dev, "fail to get pclk_rst\n"); ++ return PTR_ERR(pci->pcie_rst); ++ } ++ ++ pci->pwrup_rst = devm_reset_control_get(&pdev->dev, "pwrup_rst"); ++ if (IS_ERR(pci->pwrup_rst)) { ++ dev_err(&pdev->dev, "fail to get pwrup_rst\n"); ++ return PTR_ERR(pci->pwrup_rst); ++ } ++ } ++ ++ if (pci->drvdata->has_pcie_its_clk) { ++ pci->pcie_its = devm_clk_get(&pdev->dev, "its"); ++ if (IS_ERR(pci->pcie_its)) { ++ dev_err(&pdev->dev, "fail to get its clk\n"); ++ return PTR_ERR(pci->pcie_its); ++ } ++ ++ pci->pcie_its_rst = devm_reset_control_get(&pdev->dev, "its"); ++ if (IS_ERR(pci->pcie_its_rst)) { ++ dev_err(&pdev->dev, "fail to get its rst\n"); ++ return PTR_ERR(pci->pcie_its_rst); ++ } ++ } ++ return 0; ++} ++ ++static int sunxi_pcie_plat_combo_phy_init(struct sunxi_pcie *pci) ++{ ++ int ret; ++ ++ ret = phy_init(pci->phy); ++ if (ret) { ++ dev_err(pci->dev, "fail to init phy, err %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void sunxi_pcie_plat_combo_phy_deinit(struct sunxi_pcie *pci) ++{ ++ phy_exit(pci->phy); ++} ++ ++static void sunxi_pcie_plat_sii_int0_handler(struct sunxi_pcie_port *pp) ++{ ++ struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); ++ u32 mask, stas, irq; ++ ++ mask = sunxi_pcie_readl(pci, SII_INT_MASK0); ++ stas = sunxi_pcie_readl(pci, SII_INT_STAS0); ++ irq = mask & stas; ++ ++ if (irq & INTX_RX_ASSERT_MASK) { ++ unsigned long status = irq & INTX_RX_ASSERT_MASK; ++ u32 bit = INTX_RX_ASSERT_SHIFT; ++ for_each_set_bit_from(bit, &status, PCI_NUM_INTX + INTX_RX_ASSERT_SHIFT) { ++ /* Clear INTx status */ ++ sunxi_pcie_writel(BIT(bit), pci, SII_INT_STAS0); ++ generic_handle_domain_irq(pp->intx_irq_domain, bit - INTX_RX_ASSERT_SHIFT); ++ } ++ } ++} ++ ++static irqreturn_t sunxi_pcie_plat_sii_handler(int irq, void *arg) ++{ ++ struct sunxi_pcie_port *pp = (struct sunxi_pcie_port *)arg; ++ ++ sunxi_pcie_plat_sii_int0_handler(pp); ++ ++ sunxi_pcie_plat_irqpending(pp); ++ ++ return IRQ_HANDLED; ++} ++ ++static void sunxi_pcie_plat_dma_handle_interrupt(struct sunxi_pcie *pci, u32 ch, enum dma_dir dma_trx) ++{ ++ sunxi_pci_edma_chan_t *edma_chan = NULL; ++ sunxi_pcie_edma_callback cb = NULL; ++ void *cb_data = NULL; ++ ++ if (dma_trx == PCIE_DMA_WRITE) { ++ edma_chan = &pci->dma_wr_chn[ch]; ++ cb = edma_chan->callback; ++ cb_data = edma_chan->callback_param; ++ if (cb) ++ cb(cb_data); ++ } else if (dma_trx == PCIE_DMA_READ) { ++ edma_chan = &pci->dma_rd_chn[ch]; ++ cb = edma_chan->callback; ++ cb_data = edma_chan->callback_param; ++ if (cb) ++ cb(cb_data); ++ } else { ++ dev_err(pci->dev, "ERR: unsupported type:%d \n", dma_trx); ++ } ++ ++ if (edma_chan->cookie) ++ sunxi_pcie_dma_chan_release(edma_chan, dma_trx); ++} ++ ++#define SUNXI_PCIE_DMA_IRQ_HANDLER(name, chn, dir) \ ++static irqreturn_t sunxi_pcie_##name##_irq_handler \ ++ (int irq, void *arg) \ ++{ \ ++ struct sunxi_pcie *pci = arg; \ ++ union int_status sta = {0}; \ ++ union int_clear clr = {0}; \ ++ \ ++ sta.dword = sunxi_pcie_readl_dbi(pci, PCIE_DMA_OFFSET + \ ++ (dir ? PCIE_DMA_RD_INT_STATUS : PCIE_DMA_WR_INT_STATUS)); \ ++ \ ++ if (sta.done & BIT(chn)) { \ ++ clr.doneclr = BIT(chn); \ ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + \ ++ (dir ? PCIE_DMA_RD_INT_CLEAR : PCIE_DMA_WR_INT_CLEAR), clr.dword);\ ++ sunxi_pcie_plat_dma_handle_interrupt(pci, chn, dir); \ ++ } \ ++ \ ++ if (sta.abort & BIT(chn)) { \ ++ clr.abortclr = BIT(chn); \ ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + \ ++ (dir ? PCIE_DMA_RD_INT_CLEAR : PCIE_DMA_WR_INT_CLEAR), clr.dword);\ ++ dev_err(pci->dev, "DMA %s channel %d is abort\n", \ ++ dir ? "read":"write", chn); \ ++ } \ ++ \ ++ return IRQ_HANDLED; \ ++} ++ ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w0, 0, PCIE_DMA_WRITE) ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w1, 1, PCIE_DMA_WRITE) ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w2, 2, PCIE_DMA_WRITE) ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_w3, 3, PCIE_DMA_WRITE) ++ ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r0, 0, PCIE_DMA_READ) ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r1, 1, PCIE_DMA_READ) ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r2, 2, PCIE_DMA_READ) ++SUNXI_PCIE_DMA_IRQ_HANDLER(dma_r3, 3, PCIE_DMA_READ) ++ ++static void sunxi_pcie_plat_dma_read(struct sunxi_pcie *pci, struct dma_table *table) ++{ ++ int offset = PCIE_DMA_OFFSET + table->start.chnl * 0x200; ++ ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_ENB, ++ table->enb.dword); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_CTRL_LO, ++ table->ctx_reg.ctrllo.dword); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_CTRL_HI, ++ table->ctx_reg.ctrlhi.dword); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_XFERSIZE, ++ table->ctx_reg.xfersize); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_SAR_LO, ++ table->ctx_reg.sarptrlo); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_SAR_HI, ++ table->ctx_reg.sarptrhi); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_DAR_LO, ++ table->ctx_reg.darptrlo); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_DAR_HI, ++ table->ctx_reg.darptrhi); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_RD_WEILO, ++ table->weilo.dword); ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_DOORBELL, ++ table->start.dword); ++} ++ ++static void sunxi_pcie_plat_dma_write(struct sunxi_pcie *pci, struct dma_table *table) ++{ ++ int offset = PCIE_DMA_OFFSET + table->start.chnl * 0x200; ++ ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_ENB, ++ table->enb.dword); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_CTRL_LO, ++ table->ctx_reg.ctrllo.dword); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_CTRL_HI, ++ table->ctx_reg.ctrlhi.dword); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_XFERSIZE, ++ table->ctx_reg.xfersize); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_SAR_LO, ++ table->ctx_reg.sarptrlo); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_SAR_HI, ++ table->ctx_reg.sarptrhi); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_DAR_LO, ++ table->ctx_reg.darptrlo); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_DAR_HI, ++ table->ctx_reg.darptrhi); ++ sunxi_pcie_writel_dbi(pci, offset + PCIE_DMA_WR_WEILO, ++ table->weilo.dword); ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_DOORBELL, ++ table->start.dword); ++} ++ ++/* ++ * DMA controller: I/O and Type 0 or Type 1 configuration DMA ++ * transfers are not supported. ++ * Transfer size: 1B - 4GB ++ */ ++static void sunxi_pcie_plat_dma_start(struct dma_table *table, struct dma_trx_obj *obj) ++{ ++ struct sunxi_pcie *pci = dev_get_drvdata(obj->dev); ++ ++ if (table->dir == PCIE_DMA_READ) { ++ sunxi_pcie_plat_dma_read(pci, table); ++ } else if (table->dir == PCIE_DMA_WRITE) { ++ sunxi_pcie_plat_dma_write(pci, table); ++ } ++} ++ ++static int sunxi_pcie_plat_dma_config(struct dma_table *table, phys_addr_t src_addr, phys_addr_t dst_addr, ++ unsigned int size, enum dma_dir dma_trx, sunxi_pci_edma_chan_t *edma_chn) ++{ ++ sunxi_pci_edma_chan_t *chn = NULL; ++ ++ table->ctx_reg.ctrllo.lie = 0x1; ++ table->ctx_reg.ctrllo.rie = 0x0; ++ table->ctx_reg.ctrllo.td = 0x1; ++ table->ctx_reg.ctrlhi.dword = 0x0; ++ table->ctx_reg.xfersize = size; ++ table->ctx_reg.sarptrlo = (u32)(src_addr & 0xffffffff); ++ table->ctx_reg.sarptrhi = (u32)(src_addr >> 32); ++ table->ctx_reg.darptrlo = (u32)(dst_addr & 0xffffffff); ++ table->ctx_reg.darptrhi = (u32)(dst_addr >> 32); ++ table->start.stop = 0x0; ++ table->dir = dma_trx; ++ ++ if (!edma_chn) { ++ chn = (sunxi_pci_edma_chan_t *)sunxi_pcie_dma_chan_request(dma_trx, NULL, NULL); ++ if (!chn) { ++ dev_err(NULL, "pcie request %s channel error! \n", (dma_trx ? "DMA_READ" : "DMA_WRITE")); ++ return -ENOMEM; ++ } ++ ++ chn->cookie = true; ++ table->start.chnl = chn->chnl_num; ++ table->weilo.dword = (PCIE_WEIGHT << (5 * chn->chnl_num)); ++ } else { ++ table->start.chnl = edma_chn->chnl_num; ++ table->weilo.dword = (PCIE_WEIGHT << (5 * edma_chn->chnl_num)); ++ } ++ ++ table->enb.enb = 0x1; ++ return 0; ++} ++ ++static int sunxi_pcie_plat_request_irq(struct sunxi_pcie *sunxi_pcie, struct platform_device *pdev) ++{ ++ int irq, ret; ++ struct sunxi_pcie *pci = platform_get_drvdata(pdev); ++ struct sunxi_pcie_port *pp = &pci->pp; ++ ++ irq = platform_get_irq_byname(pdev, "sii"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ if (!pp->has_its) { ++ irq = platform_get_irq_byname(pdev, "sii"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, ++ sunxi_pcie_plat_sii_handler, IRQF_SHARED, "pcie-sii", &sunxi_pcie->pp); ++ if (ret) { ++ dev_err(&pdev->dev, "PCIe failed to request linkup IRQ\n"); ++ return ret; ++ } ++ } ++ ++ ret = sunxi_pcie_dma_get_chan(pdev); ++ if (ret) ++ return -EINVAL; ++ ++ switch (sunxi_pcie->num_edma) { ++ case 4: ++ irq = platform_get_irq_byname(pdev, "edma-w3"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w3_irq_handler, ++ IRQF_SHARED, "pcie-dma-w3", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq_byname(pdev, "edma-r3"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r3_irq_handler, ++ IRQF_SHARED, "pcie-dma-r3", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ fallthrough; ++ case 3: ++ irq = platform_get_irq_byname(pdev, "edma-w2"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w2_irq_handler, ++ IRQF_SHARED, "pcie-dma-w2", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq_byname(pdev, "edma-r2"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r2_irq_handler, ++ IRQF_SHARED, "pcie-dma-r2", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ fallthrough; ++ case 2: ++ irq = platform_get_irq_byname(pdev, "edma-w1"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w1_irq_handler, ++ IRQF_SHARED, "pcie-dma-w1", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq_byname(pdev, "edma-r1"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r1_irq_handler, ++ IRQF_SHARED, "pcie-dma-r1", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ fallthrough; ++ case 1: ++ irq = platform_get_irq_byname(pdev, "edma-w0"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_w0_irq_handler, ++ IRQF_SHARED, "pcie-dma-w0", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ irq = platform_get_irq_byname(pdev, "edma-r0"); ++ if (irq < 0) ++ return -EINVAL; ++ ++ ret = devm_request_irq(&pdev->dev, irq, sunxi_pcie_dma_r0_irq_handler, ++ IRQF_SHARED, "pcie-dma-r0", sunxi_pcie); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request PCIe DMA IRQ\n"); ++ return ret; ++ } ++ ++ break; ++ default: ++ dev_err(sunxi_pcie->dev, "Not support DMA chan_num[%d], which exceed chan_range [%d-%d]\n", ++ sunxi_pcie->num_edma, 1, 4); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int sunxi_pcie_plat_dma_init(struct sunxi_pcie *pci) ++{ ++ pci->dma_obj = sunxi_pcie_dma_obj_probe(pci->dev); ++ ++ if (IS_ERR(pci->dma_obj)) { ++ dev_err(pci->dev, "failed to prepare dma obj probe\n"); ++ return -EINVAL; ++ } ++ ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_INT_MASK, 0x0); ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_INT_MASK, 0x0); ++ return 0; ++} ++ ++static void sunxi_pcie_plat_dma_deinit(struct sunxi_pcie *pci) ++{ ++ sunxi_pcie_dma_obj_remove(pci->dev); ++ ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_WR_INT_MASK, PCIE_DMA_INT_MASK); ++ sunxi_pcie_writel_dbi(pci, PCIE_DMA_OFFSET + PCIE_DMA_RD_INT_MASK, PCIE_DMA_INT_MASK); ++} ++ ++static int sunxi_pcie_plat_parse_dts_res(struct platform_device *pdev, struct sunxi_pcie *pci) ++{ ++ struct sunxi_pcie_port *pp = &pci->pp; ++ struct device_node *np = pp->dev->of_node; ++ struct resource *dbi_res; ++ int ret; ++ ++ dbi_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi"); ++ if (!dbi_res) { ++ dev_err(&pdev->dev, "get pcie dbi failed\n"); ++ return -ENODEV; ++ } ++ ++ pci->dbi_base = devm_ioremap_resource(&pdev->dev, dbi_res); ++ if (IS_ERR(pci->dbi_base)) { ++ dev_err(&pdev->dev, "ioremap pcie dbi failed\n"); ++ return PTR_ERR(pci->dbi_base); ++ } ++ ++ pp->dbi_base = pci->dbi_base; ++ pci->app_base = pci->dbi_base + PCIE_USER_DEFINED_REGISTER; ++ ++ pci->link_gen = of_pci_get_max_link_speed(pdev->dev.of_node); ++ if (pci->link_gen < 0) { ++ dev_warn(&pdev->dev, "get pcie speed Gen failed\n"); ++ pci->link_gen = 0x1; ++ } ++ ++ pci->rst_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH); ++ if (IS_ERR(pci->rst_gpio)) ++ dev_warn(&pdev->dev, "Failed to get \"reset-gpios\"\n"); ++ else ++ gpiod_direction_output(pci->rst_gpio, 1); ++ ++ pci->wake_gpio = devm_gpiod_get(&pdev->dev, "wake", GPIOD_OUT_HIGH); ++ if (IS_ERR(pci->wake_gpio)) ++ dev_warn(&pdev->dev, "Failed to get \"wake-gpios\"\n"); ++ else ++ gpiod_direction_output(pci->wake_gpio, 1); ++ ++ pci->pcie3v3 = devm_regulator_get_optional(&pdev->dev, "pcie3v3"); ++ if (IS_ERR(pci->pcie3v3)) ++ dev_warn(&pdev->dev, "no pcie3v3 regulator found\n"); ++ ++ ret = of_property_read_u32(np, "num-lanes", &pci->lanes); ++ if (ret) { ++ dev_err(&pdev->dev, "Failed to parse the number of lanes\n"); ++ return -EINVAL; ++ } ++ ++ pp->cpu_pcie_addr_quirk = pci->drvdata->cpu_pcie_addr_quirk; ++ ++ ret = sunxi_pcie_plat_clk_get(pdev, pci); ++ if (ret) { ++ dev_err(&pdev->dev, "pcie get clk init failed\n"); ++ return -ENODEV; ++ } ++ ++ pci->phy = devm_phy_get(pci->dev, "pcie-phy"); ++ if (IS_ERR(pci->phy)) ++ return dev_err_probe(pci->dev, PTR_ERR(pci->phy), "missing PHY\n"); ++ ++ return 0; ++} ++ ++static int sunxi_pcie_plat_hw_init(struct sunxi_pcie *pci) ++{ ++ int ret; ++ ++ ret = sunxi_pcie_plat_power_on(pci); ++ if (ret) ++ return ret; ++ ++ ret = sunxi_pcie_plat_clk_setup(pci); ++ if (ret) ++ goto err0; ++ ++ ret = sunxi_pcie_plat_combo_phy_init(pci); ++ if (ret) ++ goto err1; ++ ++ return 0; ++ ++err1: ++ sunxi_pcie_plat_clk_exit(pci); ++err0: ++ sunxi_pcie_plat_power_off(pci); ++ ++ return ret; ++} ++ ++static void sunxi_pcie_plat_hw_deinit(struct sunxi_pcie *pci) ++{ ++ sunxi_pcie_plat_combo_phy_deinit(pci); ++ sunxi_pcie_plat_power_off(pci); ++ sunxi_pcie_plat_clk_exit(pci); ++} ++ ++static int sunxi_pcie_plat_probe(struct platform_device *pdev) ++{ ++ struct sunxi_pcie *pci; ++ struct sunxi_pcie_port *pp; ++ const struct sunxi_pcie_of_data *data; ++ enum sunxi_pcie_device_mode mode; ++ int ret; ++ ++ data = of_device_get_match_data(&pdev->dev); ++ mode = (enum sunxi_pcie_device_mode)data->mode; ++ ++ pci = devm_kzalloc(&pdev->dev, sizeof(*pci), GFP_KERNEL); ++ if (!pci) ++ return -ENOMEM; ++ ++ pp = &pci->pp; ++ pp->dev = &pdev->dev; ++ pci->dev = &pdev->dev; ++ pci->drvdata = data; ++ ++ ret = sunxi_pcie_plat_parse_dts_res(pdev, pci); ++ if (ret) ++ return ret; ++ ++ ret = sunxi_pcie_plat_hw_init(pci); ++ if (ret) ++ return ret; ++ ++ sunxi_pcie_plat_set_irqmask(pci); ++ platform_set_drvdata(pdev, pci); ++ ++ ret = sunxi_pcie_plat_request_irq(pci, pdev); ++ if (ret) ++ goto err0; ++ ++ pm_runtime_enable(&pdev->dev); ++ ret = pm_runtime_get_sync(&pdev->dev); ++ if (ret < 0) { ++ dev_err(&pdev->dev, "pm_runtime_get_sync failed\n"); ++ goto err1; ++ } ++ ++ ret = sunxi_pcie_plat_dma_init(pci); ++ if (ret) ++ goto err2; ++ ++ if (pci->dma_obj) { ++ pci->dma_obj->start_dma_trx_func = sunxi_pcie_plat_dma_start; ++ pci->dma_obj->config_dma_trx_func = sunxi_pcie_plat_dma_config; ++ } ++ ++ switch (pci->drvdata->mode) { ++ case SUNXI_PCIE_RC_TYPE: ++ ret = sunxi_pcie_host_add_port(pci, pdev); ++ break; ++ case SUNXI_PCIE_EP_TYPE: ++ sunxi_pcie_plat_set_mode(pci); ++ pci->ep.ops = &sunxi_ep_ops; ++ ret = sunxi_pcie_ep_init(pci); ++ break; ++ default: ++ dev_err(&pdev->dev, "INVALID device type %d\n", pci->drvdata->mode); ++ ret = -EINVAL; ++ break; ++ } ++ ++ if (ret) ++ goto err3; ++ ++ dev_info(&pdev->dev, "driver version: %s\n", SUNXI_PCIE_MODULE_VERSION); ++ ++ return 0; ++ ++err3: ++ sunxi_pcie_plat_dma_deinit(pci); ++err2: ++ pm_runtime_put(&pdev->dev); ++err1: ++ pm_runtime_disable(&pdev->dev); ++err0: ++ sunxi_pcie_plat_hw_deinit(pci); ++ ++ return ret; ++} ++ ++static void sunxi_pcie_plat_remove(struct platform_device *pdev) ++{ ++ struct sunxi_pcie *pci = platform_get_drvdata(pdev); ++ ++ sunxi_pcie_plat_hw_deinit(pci); ++ ++ pm_runtime_disable(&pdev->dev); ++ ++ pm_runtime_put(&pdev->dev); ++ ++ sunxi_pcie_plat_dma_deinit(pci); ++ ++ switch (pci->drvdata->mode) { ++ case SUNXI_PCIE_RC_TYPE: ++ sunxi_pcie_host_remove_port(pci); ++ break; ++ case SUNXI_PCIE_EP_TYPE: ++ sunxi_pcie_ep_deinit(pci); ++ break; ++ default: ++ dev_err(&pdev->dev, "unspport device type %d\n", pci->drvdata->mode); ++ break; ++ } ++ ++ sunxi_pcie_plat_ltssm_disable(pci); ++ ++} ++ ++#if IS_ENABLED(CONFIG_PM) ++static int sunxi_pcie_plat_suspend(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_pcie *pci = platform_get_drvdata(pdev); ++ ++ sunxi_pcie_plat_ltssm_disable(pci); ++ ++ usleep_range(200, 300); ++ ++ sunxi_pcie_plat_hw_deinit(pci); ++ ++ return 0; ++} ++ ++static int sunxi_pcie_plat_resume(struct device *dev) ++{ ++ struct platform_device *pdev = to_platform_device(dev); ++ struct sunxi_pcie *pci = platform_get_drvdata(pdev); ++ struct sunxi_pcie_port *pp = &pci->pp; ++ int ret; ++ ++ ret = sunxi_pcie_plat_hw_init(pci); ++ if (ret) ++ return -EINVAL; ++ ++ /* TODO */ ++ usleep_range(100, 300); ++ ++ switch (pci->drvdata->mode) { ++ case SUNXI_PCIE_RC_TYPE: ++ sunxi_pcie_plat_ltssm_disable(pci); ++ sunxi_pcie_host_setup_rc(pp); ++ ++ if (IS_ENABLED(CONFIG_PCI_MSI) && !pp->has_its) { ++ phys_addr_t pa = ALIGN_DOWN(virt_to_phys(pp), SZ_4K); ++ sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, lower_32_bits(pa)); ++ sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, upper_32_bits(pa)); ++ } ++ ++ sunxi_pcie_host_establish_link(pci); ++ sunxi_pcie_host_speed_change(pci, pci->link_gen); ++ break; ++ case SUNXI_PCIE_EP_TYPE: ++ /* TODO */ ++ break; ++ default: ++ dev_err(pci->dev, "unsupport device type %d\n", pci->drvdata->mode); ++ break; ++ } ++ ++ return 0; ++} ++ ++static struct dev_pm_ops sunxi_pcie_plat_pm_ops = { ++ .suspend = sunxi_pcie_plat_suspend, ++ .resume = sunxi_pcie_plat_resume, ++}; ++#else ++static struct dev_pm_ops sunxi_pcie_plat_pm_ops; ++#endif /* CONFIG_PM */ ++ ++static struct platform_driver sunxi_pcie_plat_driver = { ++ .driver = { ++ .name = "sunxi-pcie", ++ .owner = THIS_MODULE, ++ .of_match_table = sunxi_pcie_plat_of_match, ++ .pm = &sunxi_pcie_plat_pm_ops, ++ }, ++ .probe = sunxi_pcie_plat_probe, ++ .remove = sunxi_pcie_plat_remove, ++}; ++ ++module_platform_driver(sunxi_pcie_plat_driver); ++ ++MODULE_AUTHOR("songjundong "); ++MODULE_DESCRIPTION("Allwinner PCIe controller platform driver"); ++MODULE_VERSION(SUNXI_PCIE_MODULE_VERSION); ++MODULE_LICENSE("GPL v2"); +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/pcie-sunxi-rc.c +@@ -0,0 +1,854 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++// SPDX_License-Identifier: GPL-2.0 ++/* ++ * allwinner PCIe host controller driver ++ * ++ * Copyright (c) 2007-2022 Allwinnertech Co., Ltd. ++ * ++ * Author: songjundong ++ * ++ * 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. ++ * ++ */ ++ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++// SPDX_License-Identifier: GPL-2.0 ++/* ++ * allwinner PCIe host controller driver ++ * ++ * Copyright (c) 2007-2022 Allwinnertech Co., Ltd. ++ * ++ * Author: songjundong ++ * ++ * This software is licensed under the terms of the GNU General Public ++ * License version 2, as published by the Free Software Foundation, and ++ * may be copied, distributed, and modified under those terms. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ */ ++ ++#define SUNXI_MODNAME "pcie-rc" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pci.h" ++#include "pcie-sunxi.h" ++#include "pcie-sunxi-dma.h" ++ ++static bool sunxi_pcie_host_is_link_up(struct sunxi_pcie_port *pp) ++{ ++ if (pp->ops->is_link_up) ++ return pp->ops->is_link_up(pp); ++ else ++ return false; ++} ++ ++static int sunxi_pcie_host_rd_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 *val) ++{ ++ int ret; ++ ++ if (pp->ops->rd_own_conf) ++ ret = pp->ops->rd_own_conf(pp, where, size, val); ++ else ++ ret = sunxi_pcie_cfg_read(pp->dbi_base + where, size, val); ++ ++ return ret; ++} ++ ++int sunxi_pcie_host_wr_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 val) ++{ ++ int ret; ++ ++ if (pp->ops->wr_own_conf) ++ ret = pp->ops->wr_own_conf(pp, where, size, val); ++ else ++ ret = sunxi_pcie_cfg_write(pp->dbi_base + where, size, val); ++ ++ return ret; ++} ++ ++static int sunxi_msi_set_affinity(struct irq_data *d, const struct cpumask *mask, bool force) ++{ ++ return -EINVAL; ++} ++ ++static void sunxi_compose_msi_msg(struct irq_data *data, struct msi_msg *msg) ++{ ++ struct sunxi_pcie_port *pcie = irq_data_get_irq_chip_data(data); ++ u64 msi_target = (u64)pcie->msi_data; ++ ++ msg->address_lo = lower_32_bits(msi_target); ++ msg->address_hi = upper_32_bits(msi_target); ++ msg->data = data->hwirq; ++ ++ pr_debug("-%s:[DEBUG]: msi#%d address_hi %#x address_lo %#x\n", ++ dev_name(pcie->dev), (int)data->hwirq, msg->address_hi, msg->address_lo); ++} ++ ++static void sunxi_msi_ack_irq(struct irq_data *d) ++{ ++ /* NULL */ ++} ++ ++static struct irq_chip sunxi_msi_bottom_chip = { ++ .name = "SUNXI MSI", ++ .irq_ack = sunxi_msi_ack_irq, ++ .irq_set_affinity = sunxi_msi_set_affinity, ++ .irq_compose_msi_msg = sunxi_compose_msi_msg, ++}; ++ ++static int sunxi_msi_domain_alloc(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs, void *args) ++{ ++ struct sunxi_pcie_port *pp = domain->host_data; ++ int hwirq, i; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&pp->lock, flags); ++ ++ hwirq = bitmap_find_free_region(pp->msi_map, INT_PCI_MSI_NR, order_base_2(nr_irqs)); ++ ++ raw_spin_unlock_irqrestore(&pp->lock, flags); ++ ++ if (unlikely(hwirq < 0)) { ++ dev_err(pp->dev, "failed to alloc hwirq\n"); ++ return -ENOSPC; ++ } ++ ++ for (i = 0; i < nr_irqs; i++) ++ irq_domain_set_info(domain, virq + i, hwirq + i, ++ &sunxi_msi_bottom_chip, pp, ++ handle_simple_irq, NULL, NULL); ++ ++ return 0; ++} ++ ++static void sunxi_msi_domain_free(struct irq_domain *domain, unsigned int virq, ++ unsigned int nr_irqs) ++{ ++ struct irq_data *d = irq_domain_get_irq_data(domain, virq); ++ struct sunxi_pcie_port *pp = domain->host_data; ++ unsigned long flags; ++ ++ raw_spin_lock_irqsave(&pp->lock, flags); ++ ++ bitmap_release_region(pp->msi_map, d->hwirq, order_base_2(nr_irqs)); ++ ++ raw_spin_unlock_irqrestore(&pp->lock, flags); ++} ++ ++static const struct irq_domain_ops sunxi_domain_ops = { ++ .alloc = sunxi_msi_domain_alloc, ++ .free = sunxi_msi_domain_free, ++}; ++ ++#define SUNXI_PCIE_MSI_FLAGS_REQUIRED (MSI_FLAG_USE_DEF_DOM_OPS | \ ++ MSI_FLAG_USE_DEF_CHIP_OPS) ++#define SUNXI_PCIE_MSI_FLAGS_SUPPORTED (MSI_FLAG_MULTI_PCI_MSI | \ ++ MSI_GENERIC_FLAGS_MASK) ++ ++static const struct msi_parent_ops sunxi_pcie_msi_parent_ops = { ++ .required_flags = SUNXI_PCIE_MSI_FLAGS_REQUIRED, ++ .supported_flags = SUNXI_PCIE_MSI_FLAGS_SUPPORTED, ++ .bus_select_token = DOMAIN_BUS_PCI_MSI, ++ .chip_flags = MSI_CHIP_FLAG_SET_ACK, ++ .prefix = "SUNXI-", ++ .init_dev_msi_info = msi_lib_init_dev_msi_info, ++}; ++ ++static int sunxi_allocate_msi_domains(struct sunxi_pcie_port *pp) ++{ ++ struct irq_domain_info info = { ++ .fwnode = dev_fwnode(pp->dev), ++ .ops = &sunxi_domain_ops, ++ .host_data = pp, ++ .size = INT_PCI_MSI_NR, ++ }; ++ ++ pp->irq_domain = msi_create_parent_irq_domain(&info, &sunxi_pcie_msi_parent_ops); ++ if (!pp->irq_domain) { ++ dev_err(pp->dev, "failed to create IRQ domain\n"); ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static void sunxi_free_irq_domains(struct sunxi_pcie_port *pp) ++{ ++ if (pp->irq_domain) ++ irq_domain_remove(pp->irq_domain); ++ if (pp->intx_irq_domain) ++ irq_domain_remove(pp->intx_irq_domain); ++} ++ ++static int sunxi_pcie_msi_init(struct sunxi_pcie_port *pp) ++{ ++ u64 msi_target; ++ int ret; ++ ++ ret = dma_set_mask(pp->dev, DMA_BIT_MASK(32)); ++ if (ret) ++ dev_warn(pp->dev, "Failed to set DMA mask to 32-bit. Devices with only 32-bit MSI support may not work properly\n"); ++ ++ pp->msi_data = dma_map_single_attrs(pp->dev, &pp->msi_msg, sizeof(pp->msi_msg), ++ DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); ++ ret = dma_mapping_error(pp->dev, pp->msi_data); ++ if (ret) { ++ dev_err(pp->dev, "Failed to map MSI data\n"); ++ pp->msi_data = 0; ++ return ret; ++ } ++ ++ msi_target = (u64)pp->msi_data; ++ sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_LO, 4, lower_32_bits(msi_target)); ++ sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_ADDR_HI, 4, upper_32_bits(msi_target)); ++ ++ return 0; ++} ++ ++static void sunxi_pcie_free_msi(struct sunxi_pcie_port *pp) ++{ ++ if (pp->msi_data) ++ dma_unmap_single_attrs(pp->dev, pp->msi_data, sizeof(pp->msi_msg), ++ DMA_FROM_DEVICE, DMA_ATTR_SKIP_CPU_SYNC); ++} ++ ++static void sunxi_pcie_intx_irq_mask(struct irq_data *data) ++{ ++ struct sunxi_pcie *pcie = irq_data_get_irq_chip_data(data); ++ struct sunxi_pcie_port *pp = &pcie->pp; ++ irq_hw_number_t hwirq = irqd_to_hwirq(data); ++ unsigned long flags; ++ u32 mask, stas; ++ ++ raw_spin_lock_irqsave(&pp->lock, flags); ++ mask = sunxi_pcie_readl(pcie, SII_INT_MASK0); ++ mask &= ~INTX_RX_ASSERT(hwirq); ++ sunxi_pcie_writel(mask, pcie, SII_INT_MASK0); ++ stas = sunxi_pcie_readl(pcie, SII_INT_STAS0); ++ stas |= INTX_RX_ASSERT(hwirq); ++ sunxi_pcie_writel(stas, pcie, SII_INT_STAS0); ++ raw_spin_unlock_irqrestore(&pp->lock, flags); ++} ++ ++static void sunxi_pcie_intx_irq_unmask(struct irq_data *data) ++{ ++ struct sunxi_pcie *pcie = irq_data_get_irq_chip_data(data); ++ struct sunxi_pcie_port *pp = &pcie->pp; ++ irq_hw_number_t hwirq = irqd_to_hwirq(data); ++ unsigned long flags; ++ u32 mask, stas; ++ ++ raw_spin_lock_irqsave(&pp->lock, flags); ++ stas = sunxi_pcie_readl(pcie, SII_INT_STAS0); ++ stas |= INTX_RX_ASSERT(hwirq); ++ sunxi_pcie_writel(stas, pcie, SII_INT_STAS0); ++ mask = sunxi_pcie_readl(pcie, SII_INT_MASK0); ++ mask |= INTX_RX_ASSERT(hwirq); ++ sunxi_pcie_writel(mask, pcie, SII_INT_MASK0); ++ raw_spin_unlock_irqrestore(&pp->lock, flags); ++} ++ ++static struct irq_chip sunxi_pcie_sii_intx_chip = { ++ .name = "SUNXI-PCIe-SII-INTx", ++ .irq_enable = sunxi_pcie_intx_irq_unmask, ++ .irq_disable = sunxi_pcie_intx_irq_mask, ++ .irq_mask = sunxi_pcie_intx_irq_mask, ++ .irq_unmask = sunxi_pcie_intx_irq_unmask, ++}; ++ ++static int sunxi_pcie_intx_irq_map(struct irq_domain *domain, unsigned int irq, ++ irq_hw_number_t hwirq) ++{ ++ irq_set_chip_and_handler(irq, &sunxi_pcie_sii_intx_chip, handle_simple_irq); ++ irq_set_chip_data(irq, domain->host_data); ++ ++ return 0; ++} ++ ++static const struct irq_domain_ops intx_irq_domain_ops = { ++ .map = sunxi_pcie_intx_irq_map, ++}; ++ ++static int sunxi_allocate_intx_irq_domains(struct sunxi_pcie_port *pp) ++{ ++ struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); ++ struct device_node *intc; ++ u32 val; ++ ++ intc = of_get_child_by_name(pp->dev->of_node, "legacy-interrupt-controller"); ++ if (!intc) { ++ dev_err(pp->dev, "missing child interrupt-controller node\n"); ++ return -EINVAL; ++ } ++ ++ pp->intx_irq_domain = irq_domain_create_linear(of_fwnode_handle(intc), PCI_NUM_INTX, ++ &intx_irq_domain_ops, pci); ++ of_node_put(intc); ++ if (!pp->intx_irq_domain) { ++ dev_warn(pp->dev, "failed to add intx irq domain\n"); ++ return -EINVAL; ++ } ++ ++ /* intx irq enable */ ++ val = sunxi_pcie_readl(pci, SII_INT_MASK0); ++ val |= INTX_RX_ASSERT_MASK; ++ sunxi_pcie_writel(val, pci, SII_INT_MASK0); ++ ++ return 0; ++} ++ ++static void sunxi_pcie_prog_outbound_atu(struct sunxi_pcie_port *pp, int index, int type, ++ u64 cpu_addr, u64 pci_addr, u32 size) ++{ ++ struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); ++ unsigned int retries; ++ int val; ++ ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_BASE_OUTBOUND(index), lower_32_bits(cpu_addr)); ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_BASE_OUTBOUND(index), upper_32_bits(cpu_addr)); ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_LIMIT_OUTBOUND(index), lower_32_bits(cpu_addr + size - 1)); ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_LOWER_TARGET_OUTBOUND(index), lower_32_bits(pci_addr)); ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_UPPER_TARGET_OUTBOUND(index), upper_32_bits(pci_addr)); ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR1_OUTBOUND(index), type); ++ sunxi_pcie_writel_dbi(pci, PCIE_ATU_CR2_OUTBOUND(index), PCIE_ATU_ENABLE); ++ ++ for (retries = 0; retries < LINK_WAIT_MAX_RETRIE; retries++) { ++ val = sunxi_pcie_readl_dbi(pci, PCIE_ATU_CR2_OUTBOUND(index)); ++ ++ if (val & PCIE_ATU_ENABLE) ++ return; ++ ++ mdelay(WAIT_ATU); ++ } ++ dev_warn(pp->dev, "Outbound iATU is not being enabled\n"); ++} ++ ++static int sunxi_pcie_rd_other_conf(struct sunxi_pcie_port *pp, struct pci_bus *bus, ++ u32 devfn, int where, int size, u32 *val) ++{ ++ int ret = PCIBIOS_SUCCESSFUL, type; ++ u64 busdev; ++ ++ busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | ++ PCIE_ATU_FUNC(PCI_FUNC(devfn)); ++ ++ if (pci_is_root_bus(bus->parent)) ++ type = PCIE_ATU_TYPE_CFG0; ++ else ++ type = PCIE_ATU_TYPE_CFG1; ++ ++ sunxi_pcie_prog_outbound_atu(pp, PCIE_ATU_INDEX0, type, pp->cfg0_base, busdev, pp->cfg0_size); ++ ++ ret = sunxi_pcie_cfg_read(pp->va_cfg0_base + where, size, val); ++ ++ return ret; ++} ++ ++static int sunxi_pcie_wr_other_conf(struct sunxi_pcie_port *pp, struct pci_bus *bus, ++ u32 devfn, int where, int size, u32 val) ++{ ++ int ret = PCIBIOS_SUCCESSFUL, type; ++ u64 busdev; ++ ++ busdev = PCIE_ATU_BUS(bus->number) | PCIE_ATU_DEV(PCI_SLOT(devfn)) | ++ PCIE_ATU_FUNC(PCI_FUNC(devfn)); ++ ++ if (pci_is_root_bus(bus->parent)) ++ type = PCIE_ATU_TYPE_CFG0; ++ else ++ type = PCIE_ATU_TYPE_CFG1; ++ ++ sunxi_pcie_prog_outbound_atu(pp, PCIE_ATU_INDEX0, type, pp->cfg0_base, busdev, pp->cfg0_size); ++ ++ ret = sunxi_pcie_cfg_write(pp->va_cfg0_base + where, size, val); ++ ++ return ret; ++} ++ ++static int sunxi_pcie_valid_config(struct sunxi_pcie_port *pp, ++ struct pci_bus *bus, int dev) ++{ ++ /* If there is no link, then there is no device */ ++ if (!pci_is_root_bus(bus)) { ++ if (!sunxi_pcie_host_is_link_up(pp)) ++ return 0; ++ } else if (dev > 0) ++ /* Access only one slot on each root port */ ++ return 0; ++ ++ return 1; ++} ++ ++static int sunxi_pcie_rd_conf(struct pci_bus *bus, u32 devfn, int where, ++ int size, u32 *val) ++{ ++ struct sunxi_pcie_port *pp = (bus->sysdata); ++ int ret; ++ ++ if (!pp) ++ BUG(); ++ ++ if (!sunxi_pcie_valid_config(pp, bus, PCI_SLOT(devfn))) { ++ *val = 0xffffffff; ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ } ++ ++ if (!pci_is_root_bus(bus)) ++ ret = sunxi_pcie_rd_other_conf(pp, bus, devfn, ++ where, size, val); ++ else ++ ret = sunxi_pcie_host_rd_own_conf(pp, where, size, val); ++ ++ return ret; ++} ++ ++static int sunxi_pcie_wr_conf(struct pci_bus *bus, u32 devfn, ++ int where, int size, u32 val) ++{ ++ struct sunxi_pcie_port *pp = (bus->sysdata); ++ int ret; ++ ++ if (!pp) ++ BUG(); ++ ++ if (sunxi_pcie_valid_config(pp, bus, PCI_SLOT(devfn)) == 0) ++ return PCIBIOS_DEVICE_NOT_FOUND; ++ ++ if (!pci_is_root_bus(bus)) ++ ret = sunxi_pcie_wr_other_conf(pp, bus, devfn, ++ where, size, val); ++ else ++ ret = sunxi_pcie_host_wr_own_conf(pp, where, size, val); ++ ++ return ret; ++} ++ ++static struct pci_ops sunxi_pcie_ops = { ++ .read = sunxi_pcie_rd_conf, ++ .write = sunxi_pcie_wr_conf, ++}; ++ ++static int sunxi_pcie_host_init(struct sunxi_pcie_port *pp) ++{ ++ struct device *dev = pp->dev; ++ struct resource_entry *win; ++ struct pci_host_bridge *bridge; ++ int ret; ++ ++ bridge = devm_pci_alloc_host_bridge(dev, 0); ++ if (!bridge) { ++ dev_err(dev, "Failed to alloc host bridge\n"); ++ return -ENOMEM; ++ } ++ ++ pp->bridge = bridge; ++ /* Get the I/O and memory ranges from DT */ ++ resource_list_for_each_entry(win, &bridge->windows) { ++ switch (resource_type(win->res)) { ++ case IORESOURCE_IO: ++ pp->io_size = resource_size(win->res); ++ pp->io_bus_addr = win->res->start - win->offset; ++ pp->io_base = pci_pio_to_address(win->res->start); ++ break; ++ case 0: ++ pp->cfg0_size = resource_size(win->res); ++ pp->cfg0_base = win->res->start; ++ break; ++ } ++ } ++ ++ if (!pp->va_cfg0_base) { ++ pp->va_cfg0_base = devm_pci_remap_cfgspace(dev, ++ pp->cfg0_base, pp->cfg0_size); ++ if (!pp->va_cfg0_base) { ++ dev_err(dev, "Error with ioremap in function\n"); ++ return -ENOMEM; ++ } ++ } ++ ++ if (pp->cpu_pcie_addr_quirk) { ++ pp->cfg0_base -= PCIE_CPU_BASE; ++ pp->io_base -= PCIE_CPU_BASE; ++ } ++ ++ sunxi_allocate_intx_irq_domains(pp); ++ ++ if (pci_msi_enabled() && !pp->has_its) { ++ ret = sunxi_allocate_msi_domains(pp); ++ if (ret) ++ return ret; ++ ++ ret = sunxi_pcie_msi_init(pp); ++ if (ret) ++ return ret; ++ } ++ ++ if (pp->ops->host_init) ++ pp->ops->host_init(pp); ++ ++ bridge->sysdata = pp; ++ bridge->ops = &sunxi_pcie_ops; ++ ++ ret = pci_host_probe(bridge); ++ ++ if (ret) { ++ if (pci_msi_enabled() && !pp->has_its) { ++ sunxi_pcie_free_msi(pp); ++ } ++ sunxi_free_irq_domains(pp); ++ ++ dev_err(pp->dev, "Failed to probe host bridge\n"); ++ ++ return ret; ++ } ++ ++ return 0; ++} ++ ++void sunxi_pcie_host_setup_rc(struct sunxi_pcie_port *pp) ++{ ++ u32 val, i; ++ int atu_idx = 0; ++ struct resource_entry *entry; ++ phys_addr_t mem_base; ++ struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); ++ ++ sunxi_pcie_plat_set_rate(pci); ++ ++ /* setup RC BARs */ ++ sunxi_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x4); ++ sunxi_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x0); ++ ++ /* setup interrupt pins */ ++ val = sunxi_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE); ++ val &= PCIE_INTERRUPT_LINE_MASK; ++ val |= PCIE_INTERRUPT_LINE_ENABLE; ++ sunxi_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val); ++ ++ /* setup bus numbers */ ++ val = sunxi_pcie_readl_dbi(pci, PCI_PRIMARY_BUS); ++ val &= 0xff000000; ++ val |= 0x00ff0100; ++ sunxi_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val); ++ ++ /* setup command register */ ++ val = sunxi_pcie_readl_dbi(pci, PCI_COMMAND); ++ ++ val &= PCIE_HIGH16_MASK; ++ val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY | ++ PCI_COMMAND_MASTER | PCI_COMMAND_SERR; ++ ++ sunxi_pcie_writel_dbi(pci, PCI_COMMAND, val); ++ ++ if (pci_msi_enabled() && !pp->has_its) { ++ for (i = 0; i < 8; i++) { ++ sunxi_pcie_host_wr_own_conf(pp, PCIE_MSI_INTR_ENABLE(i), 4, ~0); ++ } ++ } ++ ++ resource_list_for_each_entry(entry, &pp->bridge->windows) { ++ if (resource_type(entry->res) != IORESOURCE_MEM) ++ continue; ++ ++ if (pp->num_ob_windows <= ++atu_idx) ++ break; ++ ++ if (pp->cpu_pcie_addr_quirk) ++ mem_base = entry->res->start - PCIE_CPU_BASE; ++ else ++ mem_base = entry->res->start; ++ ++ sunxi_pcie_prog_outbound_atu(pp, atu_idx, PCIE_ATU_TYPE_MEM, mem_base, ++ entry->res->start - entry->offset, ++ resource_size(entry->res)); ++ } ++ ++ if (pp->io_size) { ++ if (pp->num_ob_windows > ++atu_idx) ++ sunxi_pcie_prog_outbound_atu(pp, atu_idx, PCIE_ATU_TYPE_IO, pp->io_base, ++ pp->io_bus_addr, pp->io_size); ++ else ++ dev_err(pp->dev, "Resources exceed number of ATU entries (%d)", ++ pp->num_ob_windows); ++ } ++ ++ sunxi_pcie_host_wr_own_conf(pp, PCI_BASE_ADDRESS_0, 4, 0); ++ ++ sunxi_pcie_dbi_ro_wr_en(pci); ++ ++ sunxi_pcie_host_wr_own_conf(pp, PCI_CLASS_DEVICE, 2, PCI_CLASS_BRIDGE_PCI); ++ ++ sunxi_pcie_dbi_ro_wr_dis(pci); ++ ++ sunxi_pcie_host_rd_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, &val); ++ val |= PORT_LOGIC_SPEED_CHANGE; ++ sunxi_pcie_host_wr_own_conf(pp, PCIE_LINK_WIDTH_SPEED_CONTROL, 4, val); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_host_setup_rc); ++ ++static int sunxi_pcie_host_wait_for_speed_change(struct sunxi_pcie *pci) ++{ ++ u32 tmp; ++ unsigned int retries; ++ ++ for (retries = 0; retries < LINK_WAIT_MAX_RETRIE; retries++) { ++ tmp = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); ++ if (!(tmp & PORT_LOGIC_SPEED_CHANGE)) ++ return 0; ++ usleep_range(SPEED_CHANGE_USLEEP_MIN, SPEED_CHANGE_USLEEP_MAX); ++ } ++ ++ dev_err(pci->dev, "Speed change timeout\n"); ++ return -ETIMEDOUT; ++} ++ ++static int sunxi_pcie_host_read_speed(struct sunxi_pcie *pci) ++{ ++ int val, gen; ++ ++ sunxi_pcie_dbi_ro_wr_en(pci); ++ val = sunxi_pcie_readl_dbi(pci, LINK_CONTROL2_LINK_STATUS2); ++ gen = val & 0xf; ++ ++ dev_info(pci->dev, "PCIe speed of Gen%d\n", gen); ++ ++ sunxi_pcie_dbi_ro_wr_dis(pci); ++ return 0; ++} ++ ++int sunxi_pcie_host_speed_change(struct sunxi_pcie *pci, int gen) ++{ ++ u32 val; ++ u32 current_speed; ++ int ret; ++ ++ current_speed = sunxi_pcie_host_read_speed(pci); ++ ++ if (current_speed >= gen) { ++ dev_info(pci->dev, "Link already at Gen%u, skipping retrain.\n", current_speed); ++ return 0; ++ } ++ ++ dev_info(pci->dev, "Current speed Gen%u < target Gen%d. Retraining link...\n", ++ current_speed, gen); ++ ++ sunxi_pcie_dbi_ro_wr_en(pci); ++ val = sunxi_pcie_readl_dbi(pci, LINK_CONTROL2_LINK_STATUS2); ++ val &= ~0xf; ++ val |= gen; ++ sunxi_pcie_writel_dbi(pci, LINK_CONTROL2_LINK_STATUS2, val); ++ ++ val = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); ++ val &= ~PORT_LOGIC_SPEED_CHANGE; ++ sunxi_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); ++ ++ val = sunxi_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL); ++ val |= PORT_LOGIC_SPEED_CHANGE; ++ sunxi_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val); ++ ++ ret = sunxi_pcie_host_wait_for_speed_change(pci); ++ if (!ret) { ++ dev_info(pci->dev, "PCIe speed of Gen%d\n", gen); ++ } ++ else ++ dev_info(pci->dev, "PCIe speed of Gen1\n"); ++ ++ sunxi_pcie_dbi_ro_wr_dis(pci); ++ return 0; ++} ++ ++static void __sunxi_pcie_host_init(struct sunxi_pcie_port *pp) ++{ ++ struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); ++ ++ if (!sunxi_pcie_host_is_link_up(pp)) { ++ sunxi_pcie_plat_ltssm_disable(pci); ++ if (!IS_ERR(pci->rst_gpio)) { ++ gpiod_set_raw_value(pci->rst_gpio, 0); ++ msleep(100); ++ gpiod_set_raw_value(pci->rst_gpio, 1); ++ } ++ } else { ++ msleep(100); ++ } ++ ++ sunxi_pcie_host_setup_rc(pp); ++ ++ if (sunxi_pcie_host_is_link_up(pp)) { ++ dev_info(pci->dev, "pcie is already link up\n"); ++ ++ sunxi_pcie_host_read_speed(pci); ++ } else { ++ sunxi_pcie_host_establish_link(pci); ++ ++ sunxi_pcie_host_speed_change(pci, pci->link_gen); ++ } ++} ++ ++static bool sunxi_pcie_host_link_up_status(struct sunxi_pcie_port *pp) ++{ ++ u32 val; ++ int ret; ++ struct sunxi_pcie *pcie = to_sunxi_pcie_from_pp(pp); ++ val = sunxi_pcie_readl(pcie, PCIE_LINK_STAT); ++ ++ if ((val & RDLH_LINK_UP) && (val & SMLH_LINK_UP)) ++ ret = true; ++ else ++ ret = false; ++ ++ return ret; ++} ++ ++static struct sunxi_pcie_host_ops sunxi_pcie_host_ops = { ++ .is_link_up = sunxi_pcie_host_link_up_status, ++ .host_init = __sunxi_pcie_host_init, ++}; ++ ++static int sunxi_pcie_host_wait_for_link(struct sunxi_pcie_port *pp) ++{ ++ int retries; ++ ++ for (retries = 0; retries < LINK_WAIT_MAX_RETRIE; retries++) { ++ if (sunxi_pcie_host_is_link_up(pp)) { ++ dev_info(pp->dev, "pcie link up success\n"); ++ return 0; ++ } ++ usleep_range(LINK_WAIT_USLEEP_MIN, LINK_WAIT_USLEEP_MAX); ++ } ++ ++ return -ETIMEDOUT; ++} ++ ++int sunxi_pcie_host_establish_link(struct sunxi_pcie *pci) ++{ ++ struct sunxi_pcie_port *pp = &pci->pp; ++ ++ if (sunxi_pcie_host_is_link_up(pp)) { ++ dev_info(pci->dev, "pcie is already link up\n"); ++ msleep(20); ++ return 0; ++ } ++ ++ sunxi_pcie_plat_ltssm_enable(pci); ++ ++ return sunxi_pcie_host_wait_for_link(pp); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_host_establish_link); ++ ++static irqreturn_t sunxi_pcie_host_msi_irq_handler(int irq, void *arg) ++{ ++ struct sunxi_pcie_port *pp = (struct sunxi_pcie_port *)arg; ++ struct sunxi_pcie *pci = to_sunxi_pcie_from_pp(pp); ++ unsigned long val; ++ int i, pos; ++ u32 status; ++ irqreturn_t ret = IRQ_NONE; ++ ++ for (i = 0; i < MAX_MSI_CTRLS; i++) { ++ status = sunxi_pcie_readl_dbi(pci, PCIE_MSI_INTR_STATUS + (i * MSI_REG_CTRL_BLOCK_SIZE)); ++ ++ if (!status) ++ continue; ++ ++ ret = IRQ_HANDLED; ++ pos = 0; ++ val = status; ++ while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL, pos)) != MAX_MSI_IRQS_PER_CTRL) { ++ ++ /* Clear MSI interrupt first here. Otherwise some irqs will be lost or timeout */ ++ sunxi_pcie_writel_dbi(pci, ++ PCIE_MSI_INTR_STATUS + (i * MSI_REG_CTRL_BLOCK_SIZE), 1 << pos); ++ ++ generic_handle_domain_irq(pp->irq_domain, (i * MAX_MSI_IRQS_PER_CTRL) + pos); ++ ++ pos++; ++ } ++ } ++ ++ return ret; ++} ++ ++int sunxi_pcie_host_add_port(struct sunxi_pcie *pci, struct platform_device *pdev) ++{ ++ struct sunxi_pcie_port *pp = &pci->pp; ++ int ret; ++ ++ ret = of_property_read_u32(pp->dev->of_node, "num-ob-windows", &pp->num_ob_windows); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to parse num-ob-windows\n"); ++ return -EINVAL; ++ } ++ ++ pp->has_its = device_property_read_bool(&pdev->dev, "msi-map"); ++ ++ if (pci_msi_enabled() && !pp->has_its) { ++ pp->msi_irq = platform_get_irq_byname(pdev, "msi"); ++ if (pp->msi_irq < 0) ++ return pp->msi_irq; ++ ++ ret = devm_request_irq(&pdev->dev, pp->msi_irq, sunxi_pcie_host_msi_irq_handler, ++ IRQF_SHARED, "pcie-msi", pp); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to request MSI IRQ\n"); ++ return ret; ++ } ++ } ++ ++ pp->ops = &sunxi_pcie_host_ops; ++ raw_spin_lock_init(&pp->lock); ++ ++ ret = sunxi_pcie_host_init(pp); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to initialize host\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_host_add_port); ++ ++void sunxi_pcie_host_remove_port(struct sunxi_pcie *pci) ++{ ++ struct sunxi_pcie_port *pp = &pci->pp; ++ ++ if (pp->bridge->bus) { ++ pci_stop_root_bus(pp->bridge->bus); ++ pci_remove_root_bus(pp->bridge->bus); ++ } ++ ++ if (pci_msi_enabled() && !pp->has_its) { ++ sunxi_pcie_free_msi(pp); ++ } ++ sunxi_free_irq_domains(pp); ++} ++EXPORT_SYMBOL_GPL(sunxi_pcie_host_remove_port); +--- /dev/null ++++ b/drivers/pci/pcie-sunxi/pcie-sunxi.h +@@ -0,0 +1,391 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * Allwinner PCIe controller driver ++ * ++ * Copyright (C) 2022 allwinner Co., Ltd. ++ * ++ * Author: songjundong ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 as ++ * published by the Free Software Foundation. ++ */ ++#ifndef _PCIE_SUNXI_H ++#define _PCIE_SUNXI_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "pcie-sunxi-dma.h" ++ ++#define PCIE_PORT_LINK_CONTROL 0x710 ++#define PORT_LINK_MODE_MASK (0x3f << 16) ++#define PORT_LINK_MODE_1_LANES (0x1 << 16) ++#define PORT_LINK_MODE_2_LANES (0x3 << 16) ++#define PORT_LINK_MODE_4_LANES (0x7 << 16) ++#define PORT_LINK_LPBK_ENABLE (0x1 << 2) ++ ++#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C ++#define PORT_LOGIC_SPEED_CHANGE (0x1 << 17) ++#define PORT_LOGIC_LINK_WIDTH_MASK (0x1ff << 8) ++#define PORT_LOGIC_LINK_WIDTH_1_LANES (0x1 << 8) ++#define PORT_LOGIC_LINK_WIDTH_2_LANES (0x2 << 8) ++#define PORT_LOGIC_LINK_WIDTH_4_LANES (0x4 << 8) ++ ++#define PCIE_ATU_VIEWPORT 0x900 ++#define PCIE_ATU_REGION_INBOUND (0x1 << 31) ++#define PCIE_ATU_REGION_OUTBOUND (0x0 << 31) ++#define PCIE_ATU_REGION_INDEX2 (0x2 << 0) ++#define PCIE_ATU_REGION_INDEX1 (0x1 << 0) ++#define PCIE_ATU_REGION_INDEX0 (0x0 << 0) ++ ++#define PCIE_ATU_INDEX0 0x0 ++#define PCIE_ATU_INDEX1 0x1 ++#define PCIE_ATU_INDEX2 0x2 ++#define PCIE_ATU_INDEX3 0x3 ++#define PCIE_ATU_INDEX4 0x4 ++#define PCIE_ATU_INDEX5 0x5 ++#define PCIE_ATU_INDEX6 0x6 ++#define PCIE_ATU_INDEX7 0x7 ++ ++#define PCIE_EP_REBAR_SIZE_32M 0x200 ++ ++#define PCIE_ATU_CR1_OUTBOUND(reg) (0x300000 + ((reg) * 0x200)) ++#define PCIE_ATU_TYPE_MEM (0x0 << 0) ++#define PCIE_ATU_TYPE_IO (0x2 << 0) ++#define PCIE_ATU_TYPE_CFG0 (0x4 << 0) ++#define PCIE_ATU_TYPE_CFG1 (0x5 << 0) ++#define PCIE_ATU_CR2_OUTBOUND(reg) (0x300004 + ((reg) * 0x200)) ++#define PCIE_ATU_DMA_BYPASS BIT(27) ++#define PCIE_ATU_BAR_MODE_ENABLE BIT(30) ++#define PCIE_ATU_ENABLE BIT(31) ++ ++#define PCIE_ATU_LOWER_BASE_OUTBOUND(reg) (0x300008 + ((reg) * 0x200)) ++#define PCIE_ATU_UPPER_BASE_OUTBOUND(reg) (0x30000c + ((reg) * 0x200)) ++#define PCIE_ATU_LIMIT_OUTBOUND(reg) (0x300010 + ((reg) * 0x200)) ++#define PCIE_ATU_LOWER_TARGET_OUTBOUND(reg) (0x300014 + ((reg) * 0x200)) ++#define PCIE_ATU_UPPER_TARGET_OUTBOUND(reg) (0x300018 + ((reg) * 0x200)) ++ ++#define PCIE_ATU_FUNC_NUM(pf) ((pf) << 20) ++#define PCIE_ATU_CR2_INBOUND(reg) (0x300104 + ((reg) * 0x200)) ++#define PCIE_ATU_MATCH_MODE BIT(30) ++#define PCIE_ATU_FUNC_NUM_MATCH_EN BIT(19) ++ ++#define PCIE_ATU_LOWER_BASE_INBOUND(reg) (0x300108 + ((reg) * 0x200)) ++#define PCIE_ATU_UPPER_BASE_INBOUND(reg) (0x30010c + ((reg) * 0x200)) ++#define PCIE_ATU_LIMIT_INBOUND(reg) (0x300110 + ((reg) * 0x200)) ++#define PCIE_ATU_LOWER_TARGET_INBOUND(reg) (0x300114 + ((reg) * 0x200)) ++#define PCIE_ATU_UPPER_TARGET_INBOUND(reg) (0x300118 + ((reg) * 0x200)) ++ ++#define PCIE_ATU_BUS(x) (((x) & 0xff) << 24) ++#define PCIE_ATU_DEV(x) (((x) & 0x1f) << 19) ++#define PCIE_ATU_FUNC(x) (((x) & 0x7) << 16) ++ ++#define PCIE_MISC_CONTROL_1_CFG 0x8bc ++#define PCIE_TYPE1_CLASS_CODE_REV_ID_REG 0x08 ++ ++#define PCIE_ADDRESS_ALIGNING (~0x3) ++#define PCIE_HIGH_16 16 ++#define PCIE_BAR_NUM 6 ++#define PCIE_MEM_FLAGS 0x4 ++#define PCIE_IO_FLAGS 0x1 ++#define PCIE_BAR_REG 0x4 ++#define PCIE_HIGH16_MASK 0xffff0000 ++#define PCIE_LOW16_MASK 0x0000ffff ++#define PCIE_INTERRUPT_LINE_MASK 0xffff00ff ++#define PCIE_INTERRUPT_LINE_ENABLE 0x00000100 ++#define PCIE_PRIMARY_BUS_MASK 0xff000000 ++#define PCIE_PRIMARY_BUS_ENABLE 0x00010100 ++#define PCIE_MEMORY_MASK 0xfff00000 ++ ++#define PCIE_CPU_BASE 0x20000000 ++ ++#define PCIE_TYPE0_STATUS_COMMAND_REG 0x4 ++ ++#define PCIE_DBI2_BASE 0x100000 ++#define DBI2_FUNC_OFFSET 0x10000 ++#define BAR_ENABLE 0x1 ++ ++#define RESBAR_CAP_REG 0x4 /* from PCIe spec4.0 7.8.6 */ ++#define RESBAR_SIZE_MASK 0xfffff0 ++#define RESBAR_CTL_REG 0x8 ++#define RESBAR_NEXT_BAR 0x8 ++#define SIZE_OF_1MB 20 /* 2^20 = 0x100000 */ ++ ++#define PCIE_COMBO_PHY_BGR 0x04 ++#define PHY_ACLK_EN BIT(17) ++#define PHY_HCLK_EN BIT(16) ++#define PHY_TERSTN BIT(1) ++#define PHY_PW_UP_RSTN BIT(0) ++#define PCIE_COMBO_PHY_CTL 0x10 ++#define PHY_USE_SEL BIT(31) /* 0:PCIE; 1:USB3 */ ++#define PHY_CLK_SEL BIT(30) /* 0:internal clk; 1:exteral clk */ ++#define PHY_BIST_EN BIT(16) ++#define PHY_PIPE_SW BIT(9) ++#define PHY_PIPE_SEL BIT(8) /* 0:PIPE resetn ctrl by PCIE ctrl; 1:PIPE resetn ctrl by */ ++#define PHY_PIPE_CLK_INVERT BIT(4) ++#define PHY_FPGA_SYS_RSTN BIT(1) /* for PFGA */ ++#define PHY_RSTN BIT(0) ++ ++#define NEXT_CAP_PTR_MASK 0xff00 ++#define CAP_ID_MASK 0x00ff ++ ++/* ++ * Maximum number of MSI IRQs can be 256 per controller. But keep ++ * it 32 as of now. Probably we will never need more than 32. If needed, ++ * then increment it in multiple of 32. ++ */ ++#define INT_PCI_MSI_NR 32 ++#define MAX_MSI_IRQS 256 ++#define MAX_MSI_IRQS_PER_CTRL 32 ++#define MAX_MSI_CTRLS (MAX_MSI_IRQS / MAX_MSI_IRQS_PER_CTRL) ++#define MSI_REG_CTRL_BLOCK_SIZE 12 ++ ++#define PCIE_LINK_WIDTH_SPEED_CONTROL 0x80C ++#define PORT_LOGIC_SPEED_CHANGE (0x1 << 17) ++#define LINK_CONTROL2_LINK_STATUS2 0xa0 ++/* Parameters for the waiting for link up routine */ ++#define LINK_WAIT_MAX_RETRIE 20 ++#define LINK_WAIT_USLEEP_MIN 90000 ++#define LINK_WAIT_USLEEP_MAX 100000 ++#define SPEED_CHANGE_USLEEP_MIN 100 ++#define SPEED_CHANGE_USLEEP_MAX 1000 ++#define WAIT_ATU 1 ++ ++#define PCIE_MSI_ADDR_LO 0x820 ++#define PCIE_MSI_ADDR_HI 0x824 ++#define PCIE_MSI_INTR_ENABLE(reg) (0x828 + ((reg) * 0x0c)) ++#define PCIE_MSI_INTR_MASK 0x82C ++#define PCIE_MSI_INTR_STATUS 0x830 ++ ++#define PCIE_CTRL_MGMT_BASE 0x900000 ++ ++#define PCIE_USER_DEFINED_REGISTER 0x400000 ++#define PCIE_VER 0x00 ++#define PCIE_ADDR_PAGE_CFG 0x04 ++#define PCIE_AWMISC_CTRL 0x200 ++#define PCIE_ARMISC_CTRL 0x220 ++#define PCIE_LTSSM_CTRL 0xc00 ++#define PCIE_LINK_TRAINING BIT(0) /* 0:disable; 1:enable */ ++#define DEVICE_TYPE_MASK GENMASK(7, 4) ++#define DEVICE_TYPE_RC BIT(6) ++#define PCIE_INT_ENABLE_CLR 0xE04 /* BIT(1):RDLH_LINK_MASK; BIT(0):SMLH_LINK_MASK */ ++#define PCIE_LINK_STAT 0xE0C /* BIT(1):RDLH_LINK; BIT(0):SMLH_LINK */ ++#define RDLH_LINK_UP BIT(1) ++#define SMLH_LINK_UP BIT(0) ++#define PCIE_LINK_INT_EN (BIT(0) | BIT(1)) ++ ++#define SII_INT_MASK0 0x0e00 ++#define SII_INT_STAS0 0x0e08 ++ #define INTX_TX_DEASSERT_MASK GENMASK(28, 25) ++ #define INTX_TX_DEASSERT_SHIFT 25 ++ #define INTX_TX_DEASSERT(x) BIT((x) + INTX_TX_DEASSERT_SHIFT) ++ #define INTX_TX_ASSERT_MASK GENMASK(24, 21) ++ #define INTX_TX_ASSERT_SHIFT 21 ++ #define INTX_TX_ASSERT(x) BIT((x) + INTX_TX_ASSERT_SHIFT) ++ #define INTX_RX_DEASSERT_MASK GENMASK(12, 9) ++ #define INTX_RX_DEASSERT_SHIFT 9 ++ #define INTX_RX_DEASSERT(x) BIT((x) + INTX_RX_DEASSERT_SHIFT) ++ #define INTX_RX_ASSERT_MASK GENMASK(8, 5) ++ #define INTX_RX_ASSERT_SHIFT 5 ++ #define INTX_RX_ASSERT(x) BIT((x) + INTX_RX_ASSERT_SHIFT) ++ ++#define PCIE_PHY_CFG 0x800 ++#define SYS_CLK 0 ++#define PAD_CLK 1 ++#define PCIE_LINK_UP_MASK (0x3<<16) ++ ++#define PCIE_RC_RP_ATS_BASE 0x400000 ++ ++#define SUNXI_PCIE_BAR_CFG_CTRL_DISABLED 0x0 ++#define SUNXI_PCIE_BAR_CFG_CTRL_IO_32BITS 0x1 ++#define SUNXI_PCIE_BAR_CFG_CTRL_MEM_32BITS 0x4 ++#define SUNXI_PCIE_BAR_CFG_CTRL_PREFETCH_MEM_32BITS 0x5 ++#define SUNXI_PCIE_BAR_CFG_CTRL_MEM_64BITS 0x6 ++#define SUNXI_PCIE_BAR_CFG_CTRL_PREFETCH_MEM_64BITS 0x7 ++ ++#define SUNXI_PCIE_EP_MSI_CTRL_REG 0x90 ++#define SUNXI_PCIE_EP_MSI_CTRL_MMC_OFFSET 17 ++#define SUNXI_PCIE_EP_MSI_CTRL_MMC_MASK GENMASK(19, 17) ++#define SUNXI_PCIE_EP_MSI_CTRL_MME_OFFSET 20 ++#define SUNXI_PCIE_EP_MSI_CTRL_MME_MASK GENMASK(22, 20) ++#define SUNXI_PCIE_EP_MSI_CTRL_ME BIT(16) ++#define SUNXI_PCIE_EP_MSI_CTRL_MASK_MSI_CAP BIT(24) ++#define SUNXI_PCIE_EP_DUMMY_IRQ_ADDR 0x1 ++ ++#define PCIE_PHY_FUNC_CFG (PCIE_CTRL_MGMT_BASE + 0x2c0) ++#define PCIE_RC_BAR_CONF (PCIE_CTRL_MGMT_BASE + 0x300) ++ ++enum sunxi_pcie_device_mode { ++ SUNXI_PCIE_EP_TYPE, ++ SUNXI_PCIE_RC_TYPE, ++}; ++ ++struct sunxi_pcie_of_data { ++ const struct sunxi_pcie_ep_ops *ops; ++ enum sunxi_pcie_device_mode mode; ++ u32 func_offset; ++ bool cpu_pcie_addr_quirk; ++ bool has_pcie_slv_clk; ++ bool need_pcie_rst; ++ bool pcie_slv_clk_400m; ++ bool has_pcie_its_clk; ++}; ++ ++struct sunxi_pcie_ep_func { ++ struct list_head list; ++ u8 func_no; ++ u8 msi_cap; ++ u8 msix_cap; ++}; ++ ++struct sunxi_pcie_ep { ++ struct pci_epc *epc; ++ struct list_head func_list; ++ const struct sunxi_pcie_ep_ops *ops; ++ phys_addr_t phys_base; ++ size_t addr_size; ++ size_t page_size; ++ u8 bar_to_atu[PCI_STD_NUM_BARS]; ++ phys_addr_t *outbound_addr; ++ u32 num_ib_windows; ++ u32 num_ob_windows; ++ unsigned long *ib_window_map; ++ unsigned long *ob_window_map; ++ void __iomem *msi_mem; ++ phys_addr_t msi_mem_phys; ++ struct pci_epf_bar *epf_bar[PCI_STD_NUM_BARS]; ++}; ++ ++struct sunxi_pcie_ep_ops { ++ void (*ep_init)(struct sunxi_pcie_ep *ep); ++ int (*raise_irq)(struct sunxi_pcie_ep *ep, u8 func_no, ++ unsigned int type, u16 interrupt_num); ++ const struct pci_epc_features *(*get_features)(struct sunxi_pcie_ep *ep); ++ unsigned int (*func_conf_select)(struct sunxi_pcie_ep *ep, u8 func_no); ++}; ++ ++struct sunxi_pcie_port { ++ struct device *dev; ++ void __iomem *dbi_base; ++ u64 cfg0_base; ++ void __iomem *va_cfg0_base; ++ u32 cfg0_size; ++ resource_size_t io_base; ++ phys_addr_t io_bus_addr; ++ u32 io_size; ++ u32 num_ob_windows; ++ struct sunxi_pcie_host_ops *ops; ++ int msi_irq; ++ struct irq_domain *intx_irq_domain; ++ struct irq_domain *irq_domain; ++ u16 msi_msg; ++ dma_addr_t msi_data; ++ struct pci_host_bridge *bridge; ++ raw_spinlock_t lock; ++ unsigned long msi_map[BITS_TO_LONGS(INT_PCI_MSI_NR)]; ++ bool has_its; ++ bool cpu_pcie_addr_quirk; ++}; ++ ++struct sunxi_pci_edma_chan; ++ ++struct sunxi_pcie { ++ struct device *dev; ++ void __iomem *dbi_base; ++ void __iomem *app_base; ++ int link_gen; ++ struct sunxi_pcie_port pp; ++ struct sunxi_pcie_ep ep; ++ struct clk *pcie_aux; ++ struct clk *pcie_slv; ++ struct clk *pcie_its; ++ struct reset_control *pcie_rst; ++ struct reset_control *pwrup_rst; ++ struct reset_control *pcie_its_rst; ++ struct phy *phy; ++ struct dma_trx_obj *dma_obj; ++ const struct sunxi_pcie_of_data *drvdata; ++ struct gpio_desc *rst_gpio; ++ struct gpio_desc *wake_gpio; ++ u32 lanes; ++ u32 num_edma; ++ unsigned long *rd_edma_map; ++ unsigned long *wr_edma_map; ++ struct sunxi_pci_edma_chan *dma_wr_chn; ++ struct sunxi_pci_edma_chan *dma_rd_chn; ++ struct regulator *pcie3v3; ++}; ++ ++#define to_sunxi_pcie_from_pp(x) \ ++ container_of((x), struct sunxi_pcie, pp) ++ ++#define to_sunxi_pcie_from_ep(endpoint) \ ++ container_of((endpoint), struct sunxi_pcie, ep) ++ ++struct sunxi_pcie_host_ops { ++ void (*readl_rc)(struct sunxi_pcie_port *pp, void __iomem *dbi_base, u32 *val); ++ void (*writel_rc)(struct sunxi_pcie_port *pp, u32 val, void __iomem *dbi_base); ++ int (*rd_own_conf)(struct sunxi_pcie_port *pp, int where, int size, u32 *val); ++ int (*wr_own_conf)(struct sunxi_pcie_port *pp, int where, int size, u32 val); ++ bool (*is_link_up)(struct sunxi_pcie_port *pp); ++ void (*host_init)(struct sunxi_pcie_port *pp); ++ void (*scan_bus)(struct sunxi_pcie_port *pp); ++}; ++ ++void sunxi_pcie_plat_set_rate(struct sunxi_pcie *pci); ++void sunxi_pcie_write_dbi(struct sunxi_pcie *pci, u32 reg, size_t size, u32 val); ++u32 sunxi_pcie_read_dbi(struct sunxi_pcie *pci, u32 reg, size_t size); ++void sunxi_pcie_plat_ltssm_enable(struct sunxi_pcie *pci); ++void sunxi_pcie_plat_ltssm_disable(struct sunxi_pcie *pci); ++int sunxi_pcie_cfg_write(void __iomem *addr, int size, u32 val); ++int sunxi_pcie_cfg_read(void __iomem *addr, int size, u32 *val); ++ ++#if IS_ENABLED(CONFIG_PCIE_SUN55I_RC) ++int sunxi_pcie_host_add_port(struct sunxi_pcie *pci, struct platform_device *pdev); ++void sunxi_pcie_host_remove_port(struct sunxi_pcie *pci); ++int sunxi_pcie_host_speed_change(struct sunxi_pcie *pci, int gen); ++int sunxi_pcie_host_wr_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 val); ++int sunxi_pcie_host_establish_link(struct sunxi_pcie *pci); ++void sunxi_pcie_host_setup_rc(struct sunxi_pcie_port *pp); ++#else ++static inline int sunxi_pcie_host_add_port(struct sunxi_pcie *pci, struct platform_device *pdev) {return 0; } ++static inline void sunxi_pcie_host_remove_port(struct sunxi_pcie *pci) {} ++static inline int sunxi_pcie_host_speed_change(struct sunxi_pcie *pci, int gen) {return 0; } ++static inline int sunxi_pcie_host_wr_own_conf(struct sunxi_pcie_port *pp, int where, int size, u32 val) {return 0; } ++static inline int sunxi_pcie_host_establish_link(struct sunxi_pcie *pci) {return 0; } ++static inline void sunxi_pcie_host_setup_rc(struct sunxi_pcie_port *pp) {} ++#endif ++ ++#if IS_ENABLED(CONFIG_AW_PCIE_EP) ++int sunxi_pcie_ep_init(struct sunxi_pcie *pci); ++void sunxi_pcie_ep_deinit(struct sunxi_pcie *pci); ++#else ++static inline int sunxi_pcie_ep_init(struct sunxi_pcie *pci) {return 0; } ++static inline void sunxi_pcie_ep_deinit(struct sunxi_pcie *pci) {} ++#endif ++ ++void sunxi_pcie_writel(u32 val, struct sunxi_pcie *pcie, u32 offset); ++u32 sunxi_pcie_readl(struct sunxi_pcie *pcie, u32 offset); ++void sunxi_pcie_writel_dbi(struct sunxi_pcie *pci, u32 reg, u32 val); ++u32 sunxi_pcie_readl_dbi(struct sunxi_pcie *pci, u32 reg); ++void sunxi_pcie_writew_dbi(struct sunxi_pcie *pci, u32 reg, u16 val); ++u16 sunxi_pcie_readw_dbi(struct sunxi_pcie *pci, u32 reg); ++void sunxi_pcie_writeb_dbi(struct sunxi_pcie *pci, u32 reg, u8 val); ++u8 sunxi_pcie_readb_dbi(struct sunxi_pcie *pci, u32 reg); ++void sunxi_pcie_dbi_ro_wr_en(struct sunxi_pcie *pci); ++void sunxi_pcie_dbi_ro_wr_dis(struct sunxi_pcie *pci); ++u8 sunxi_pcie_plat_find_capability(struct sunxi_pcie *pci, u8 cap); ++int sunxi_cleanup_uboot_msi_config(struct sunxi_pcie_port *pp); ++ ++#endif /* _PCIE_SUNXI_H */ +--- /dev/null ++++ b/include/sunxi-gpio.h +@@ -0,0 +1,188 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */ ++/* ++ * (C) Copyright 2015-2020 ++ * Allwinner Technology Co., Ltd. ++ * Wim Hwang ++ * ++ * sunxi gpio utils ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of ++ * the License, or (at your option) any later version. ++ */ ++ ++#ifndef __SW_GPIO_H ++#define __SW_GPIO_H ++ ++#define SUNXI_PINCTRL "pio" ++#define SUNXI_R_PINCTRL "r_pio" ++#include ++ ++/* pin group base number name space, ++ * the max pin number : 26*32=832. ++ */ ++#define SUNXI_BANK_SIZE 32 ++#define SUNXI_PA_BASE 0 ++#define SUNXI_PB_BASE 32 ++#define SUNXI_PC_BASE 64 ++#define SUNXI_PD_BASE 96 ++#define SUNXI_PE_BASE 128 ++#define SUNXI_PF_BASE 160 ++#define SUNXI_PG_BASE 192 ++#define SUNXI_PH_BASE 224 ++#define SUNXI_PI_BASE 256 ++#define SUNXI_PJ_BASE 288 ++#define SUNXI_PK_BASE 320 ++#define SUNXI_PL_BASE 352 ++#define SUNXI_PM_BASE 384 ++#define SUNXI_PN_BASE 416 ++#define SUNXI_PO_BASE 448 ++#define AXP_PIN_BASE 1024 ++ ++#define SUNXI_PIN_NAME_MAX_LEN 8 ++ ++/* sunxi gpio name space */ ++#define GPIOA(n) (SUNXI_PA_BASE + (n)) ++#define GPIOB(n) (SUNXI_PB_BASE + (n)) ++#define GPIOC(n) (SUNXI_PC_BASE + (n)) ++#define GPIOD(n) (SUNXI_PD_BASE + (n)) ++#define GPIOE(n) (SUNXI_PE_BASE + (n)) ++#define GPIOF(n) (SUNXI_PF_BASE + (n)) ++#define GPIOG(n) (SUNXI_PG_BASE + (n)) ++#define GPIOH(n) (SUNXI_PH_BASE + (n)) ++#define GPIOI(n) (SUNXI_PI_BASE + (n)) ++#define GPIOJ(n) (SUNXI_PJ_BASE + (n)) ++#define GPIOK(n) (SUNXI_PK_BASE + (n)) ++#define GPIOL(n) (SUNXI_PL_BASE + (n)) ++#define GPIOM(n) (SUNXI_PM_BASE + (n)) ++#define GPION(n) (SUNXI_PN_BASE + (n)) ++#define GPIOO(n) (SUNXI_PO_BASE + (n)) ++#define GPIO_AXP(n) (AXP_PIN_BASE + (n)) ++ ++/* sunxi specific input/output/eint functions */ ++#define SUNXI_PIN_INPUT_FUNC (0) ++#define SUNXI_PIN_OUTPUT_FUNC (1) ++#define SUNXI_PIN_EINT_FUNC (6) ++#define SUNXI_PIN_IO_DISABLE (7) ++ ++/* axp group base number name space, ++ * axp pinctrl number space coherent to sunxi-pinctrl. ++ */ ++#define AXP_PINCTRL "axp-pinctrl" ++#define AXP_CFG_GRP (0xFFFF) ++#define AXP_PIN_INPUT_FUNC (0) ++#define AXP_PIN_OUTPUT_FUNC (1) ++#define IS_AXP_PIN(pin) (pin >= AXP_PIN_BASE) ++ ++/* sunxi specific pull up/down */ ++enum sunxi_pull_up_down { ++ SUNXI_PULL_DISABLE = 0, ++ SUNXI_PULL_UP, ++ SUNXI_PULL_DOWN, ++}; ++ ++/* sunxi specific data types */ ++enum sunxi_data_type { ++ SUNXI_DATA_LOW = 0, ++ SUNXI_DATA_HIGH = 0, ++}; ++ ++/* sunxi specific pull status */ ++enum sunxi_pin_pull { ++ SUNXI_PIN_PULL_DISABLE = 0x00, ++ SUNXI_PIN_PULL_UP = 0x01, ++ SUNXI_PIN_PULL_DOWN = 0x02, ++ SUNXI_PIN_PULL_RESERVED = 0x03, ++}; ++ ++/* sunxi specific driver levels */ ++enum sunxi_pin_drv_level { ++ SUNXI_DRV_LEVEL0 = 10, ++ SUNXI_DRV_LEVEL1 = 20, ++ SUNXI_DRV_LEVEL2 = 30, ++ SUNXI_DRV_LEVEL3 = 40, ++}; ++ ++/* sunxi specific data bit status */ ++enum sunxi_pin_data_status { ++ SUNXI_PIN_DATA_LOW = 0x00, ++ SUNXI_PIN_DATA_HIGH = 0x01, ++}; ++ ++/* sunxi pin interrupt trigger mode */ ++enum sunxi_pin_int_trigger_mode { ++ SUNXI_PIN_EINT_POSITIVE_EDGE = 0x0, ++ SUNXI_PIN_EINT_NEGATIVE_EDGE = 0x1, ++ SUNXI_PIN_EINT_HIGN_LEVEL = 0x2, ++ SUNXI_PIN_EINT_LOW_LEVEL = 0x3, ++ SUNXI_PIN_EINT_DOUBLE_EDGE = 0x4 ++}; ++ ++/* the source clock of pin int */ ++enum sunxi_pin_int_source_clk { ++ SUNXI_PIN_INT_SRC_CLK_32K = 0x0, ++ SUNXI_PIN_INT_SRC_CLK_24M = 0x1 ++}; ++ ++/* ++ * pin configuration (pull up/down and drive strength) type and its value are ++ * packed together into a 32-bits. The lower 8-bits represent the configuration ++ * type and the upper 24-bits hold the value of the configuration type. ++ */ ++#define SUNXI_PINCFG_PACK(type, value) (((value) << 8) | (type & 0xFF)) ++#define SUNXI_PINCFG_UNPACK_TYPE(cfg) ((cfg) & 0xFF) ++#define SUNXI_PINCFG_UNPACK_VALUE(cfg) (((cfg) & 0xFFFFFF00) >> 8) ++ ++static inline int sunxi_gpio_to_name(int gpio, char *name) ++{ ++ int bank, index; ++ ++ if (!name) ++ return -EINVAL; ++ ++ if (IS_AXP_PIN(gpio)) { ++ /* axp gpio name like this : GPIO0/GPIO1/.. */ ++ index = gpio - AXP_PIN_BASE; ++ sprintf(name, "GPIO%d", index); ++ } else { ++ /* sunxi gpio name like this : PA0/PA1/PB0 */ ++ bank = gpio / SUNXI_BANK_SIZE; ++ index = gpio % SUNXI_BANK_SIZE; ++ sprintf(name, "P%c%d", ('A' + bank), index); ++ } ++ ++ return 0; ++} ++ ++/* pio end, invalid macro */ ++#define GPIO_INDEX_INVALID (0xFFFFFFF0) ++#define GPIO_CFG_INVALID (0xEEEEEEEE) ++#define GPIO_PULL_INVALID (0xDDDDDDDD) ++#define GPIO_DRVLVL_INVALID (0xCCCCCCCC) ++#define IRQ_NUM_INVALID (0xFFFFFFFF) ++#define AXP_PORT_VAL (0x0000FFFF) ++ ++/* pio default macro */ ++#define GPIO_PULL_DEFAULT ((u32)-1) ++#define GPIO_DRVLVL_DEFAULT ((u32)-1) ++#define GPIO_DATA_DEFAULT ((u32)-1) ++ ++/* ++ * struct gpio_config - gpio config info ++ * @gpio: gpio global index, must be unique ++ * @mul_sel: multi sel val: 0 - input, 1 - output. ++ * @pull: pull val: 0 - pull up/down disable, 1 - pull up ++ * @drv_level: driver level val: 0 - level 0, 1 - level 1 ++ * @data: data val: 0 - low, 1 - high, only valid when mul_sel is input/output ++ */ ++struct gpio_config { ++ u32 data; ++ u32 gpio; ++ u32 mul_sel; ++ u32 pull; ++ u32 drv_level; ++}; ++ ++#endif diff --git a/target/linux/sunxi/patches-6.18/130-04-arm64-dts-sun55i-dtsi-add-iommu-usbc-pcie-combophy-nodes.patch b/target/linux/sunxi/patches-6.18/130-04-arm64-dts-sun55i-dtsi-add-iommu-usbc-pcie-combophy-nodes.patch new file mode 100644 index 0000000000..2b52146e6e --- /dev/null +++ b/target/linux/sunxi/patches-6.18/130-04-arm64-dts-sun55i-dtsi-add-iommu-usbc-pcie-combophy-nodes.patch @@ -0,0 +1,117 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marvin Wewer +Date: Mon, 5 Jan 2026 21:41:18 +0000 +Subject: arm64: dts: allwinner: sun55i-a523: add iommu and PCIe/USB-C nodes + +Signed-off-by: Marvin Wewer +--- + arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi | 84 ++++++++++ + 1 file changed, 84 insertions(+) + +--- a/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi ++++ b/arch/arm64/boot/dts/allwinner/sun55i-a523.dtsi +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + #include + + / { +@@ -122,6 +123,17 @@ + ; + }; + ++ iommu: iommu@2010000 { ++ compatible = "allwinner,sun55i-a523-iommu"; ++ reg = <0x0 0x02010000 0x0 0x1000>; ++ interrupts = ; ++ interrupt-names = "iommu-irq"; ++ clocks = <&ccu CLK_IOMMU>; ++ clock-names = "iommu"; ++ /* clock-frequency = <24000000>; */ ++ #iommu-cells = <2>; ++ }; ++ + soc { + compatible = "simple-bus"; + #address-cells = <1>; +@@ -860,6 +872,78 @@ + }; + }; + ++ usbc1: usbc1@11 { ++ device_type = "usbc1"; ++ reg = <0x11 0x1000>; ++ usb_regulator_io = "nocare"; ++ usb_wakeup_suspend = <0>; ++ status = "disabled"; ++ }; ++ ++ combophy: phy@4f00000 { ++ compatible = "allwinner,inno-combphy"; ++ reg = <0x04f00000 0x80000>, /* Sub-System Application Registers */ ++ <0x04f80000 0x80000>; /* Combo INNO PHY Registers */ ++ reg-names = "phy-ctl", "phy-clk"; ++ power-domains = <&pck600 PD_PCIE>; ++ phy_refclk_sel = <0>; /* 0:internal clk; 1:external clk */ ++ clocks = <&ccu CLK_USB3_REF>, <&ccu CLK_PLL_PERIPH0_200M>; ++ clock-names = "phyclk_ref","refclk_par"; ++ resets = <&ccu RST_BUS_PCIE_USB3>; ++ reset-names = "phy_rst"; ++ #phy-cells = <1>; ++ status = "disabled"; ++ }; ++ ++ pcie: pcie@4800000 { ++ compatible = "allwinner,sunxi-pcie-v210-rc"; ++ #address-cells = <3>; ++ #size-cells = <2>; ++ bus-range = <0x0 0xff>; ++ reg = <0x04800000 0x480000>; ++ reg-names = "dbi"; ++ device_type = "pci"; ++ ranges = <0x00000800 0 0x20000000 0x20000000 0 0x01000000 ++ 0x81000000 0 0x21000000 0x21000000 0 0x01000000 ++ 0x82000000 0 0x22000000 0x22000000 0 0x0e000000>; ++ num-lanes = <1>; ++ phys = <&combophy PHY_TYPE_PCIE>; ++ phy-names = "pcie-phy"; ++ interrupts = , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ , ++ ; ++ interrupt-names = "msi", "sii", "edma-w0", "edma-w1", "edma-w2", "edma-w3", ++ "edma-r0", "edma-r1", "edma-r2", "edma-r3"; ++ #interrupt-cells = <1>; ++ interrupt-map-mask = <0 0 0 7>; ++ interrupt-map = <0 0 0 1 &pcie_intc 0>, ++ <0 0 0 2 &pcie_intc 1>, ++ <0 0 0 3 &pcie_intc 2>, ++ <0 0 0 4 &pcie_intc 3>; ++ num-edma = <4>; ++ max-link-speed = <2>; ++ num-ib-windows = <8>; ++ num-ob-windows = <8>; ++ linux,pci-domain = <0>; ++ power-domains = <&pck600 PD_PCIE>; ++ clocks = <&osc24M>, <&ccu CLK_PCIE_AUX>; ++ clock-names = "hosc", "pclk_aux"; ++ status = "disabled"; ++ ++ pcie_intc: legacy-interrupt-controller { ++ interrupt-controller; ++ #address-cells = <0>; ++ #interrupt-cells = <1>; ++ }; ++ }; ++ + ppu: power-controller@7001400 { + compatible = "allwinner,sun55i-a523-ppu"; + reg = <0x07001400 0x400>; diff --git a/target/linux/sunxi/patches-6.18/131-arm64-dts-allwinner-a527-Radxa-A5E-improvements.patch b/target/linux/sunxi/patches-6.18/131-01-arm64-dts-allwinner-a527-Radxa-A5E-improvements.patch similarity index 100% rename from target/linux/sunxi/patches-6.18/131-arm64-dts-allwinner-a527-Radxa-A5E-improvements.patch rename to target/linux/sunxi/patches-6.18/131-01-arm64-dts-allwinner-a527-Radxa-A5E-improvements.patch diff --git a/target/linux/sunxi/patches-6.18/131-02-arm64-dts-sun55i-a527-cubie-a5e-enable-usbc-pcie-combophy.patch b/target/linux/sunxi/patches-6.18/131-02-arm64-dts-sun55i-a527-cubie-a5e-enable-usbc-pcie-combophy.patch new file mode 100644 index 0000000000..c4dcb9f21c --- /dev/null +++ b/target/linux/sunxi/patches-6.18/131-02-arm64-dts-sun55i-a527-cubie-a5e-enable-usbc-pcie-combophy.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marvin Wewer +Date: Sun, 11 Jan 2026 12:11:10 +0000 +Subject: arm64: dts: allwinner: a527-cubie-a5e: enable PCIe/USB-C combophy + +Signed-off-by: Marvin Wewer +--- +--- + arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts | 58 ++++++++++ + 1 file changed, 58 insertions(+) + +--- a/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts ++++ b/arch/arm64/boot/dts/allwinner/sun55i-a527-cubie-a5e.dts +@@ -77,6 +77,39 @@ + gpio = <&r_pio 0 8 GPIO_ACTIVE_HIGH>; /* PL8 */ + enable-active-high; + }; ++ ++ reg_pcie_vcc3v3: regulator-pcie-vcc3v3 { ++ compatible = "regulator-fixed"; ++ regulator-name = "pcie-3v3"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-enable-ramp-delay = <1000>; ++ regulator-always-on; ++ regulator-boot-on; ++ gpio = <&r_pio 0 11 GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; ++ ++ gma340_oe: gma340-oe { ++ compatible = "regulator-fixed"; ++ regulator-name = "gma340-oe"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-always-on; ++ regulator-boot-on; ++ gpio = <&pio 1 7 GPIO_ACTIVE_LOW>; ++ }; ++ ++ gma340_pcie: gma340-pcie { ++ compatible = "regulator-fixed"; ++ regulator-name = "gma340-pcie"; ++ regulator-min-microvolt = <3300000>; ++ regulator-max-microvolt = <3300000>; ++ regulator-always-on; ++ regulator-boot-on; ++ gpio = <&pio 1 6 GPIO_ACTIVE_HIGH>; ++ enable-active-high; ++ }; + }; + + &cpu0 { +@@ -430,3 +463,28 @@ + usb1_vbus-supply = <®_usb_vbus>; + status = "okay"; + }; ++ ++/* PCIE and USB Switch */ ++&combophy { ++ resets = <&ccu RST_BUS_PCIE_USB3>; ++ phy_use_sel = <0>; /* 0:PCIE; 1:USB3 */ ++ status = "okay"; ++}; ++ ++&pcie { ++ reset-gpios = <&pio 7 11 GPIO_ACTIVE_HIGH>; ++ wake-gpios = <&pio 7 12 GPIO_ACTIVE_HIGH>; ++ num-lanes = <1>; ++ clk-freq-100M; ++ pcie3v3-supply = <®_pcie_vcc3v3>; ++ status = "okay"; ++}; ++ ++&usbc1 { ++ device_type = "usbc1"; ++ usb_regulator_io = "nocare"; ++ usb_wakeup_suspend = <1>; ++ wakeup-source; ++ status = "okay"; ++}; ++ diff --git a/target/linux/sunxi/patches-6.18/433-arm64-dts-allwinner-add-LED-aliases-to-Cubie-A5E.patch b/target/linux/sunxi/patches-6.18/433-arm64-dts-allwinner-add-LED-aliases-to-Cubie-A5E.patch index 356f4d5a34..63748c365f 100644 --- a/target/linux/sunxi/patches-6.18/433-arm64-dts-allwinner-add-LED-aliases-to-Cubie-A5E.patch +++ b/target/linux/sunxi/patches-6.18/433-arm64-dts-allwinner-add-LED-aliases-to-Cubie-A5E.patch @@ -37,7 +37,7 @@ Signed-off-by: Tianling Shen }; use-led { -@@ -129,6 +131,25 @@ +@@ -162,6 +164,25 @@ reset-gpios = <&pio 7 8 GPIO_ACTIVE_LOW>; /* PH8 */ reset-assert-us = <10000>; reset-deassert-us = <150000>; @@ -63,7 +63,7 @@ Signed-off-by: Tianling Shen }; }; -@@ -139,6 +160,25 @@ +@@ -172,6 +193,25 @@ reset-gpios = <&pio 9 16 GPIO_ACTIVE_LOW>; /* PJ16 */ reset-assert-us = <10000>; reset-deassert-us = <150000>; diff --git a/target/linux/sunxi/patches-6.18/702-net-phy-add-driver-for-MAXIO-phy.patch b/target/linux/sunxi/patches-6.18/702-net-phy-add-driver-for-MAXIO-phy.patch index 44061afbcb..cd00c1efd4 100644 --- a/target/linux/sunxi/patches-6.18/702-net-phy-add-driver-for-MAXIO-phy.patch +++ b/target/linux/sunxi/patches-6.18/702-net-phy-add-driver-for-MAXIO-phy.patch @@ -10,7 +10,7 @@ /* As long as the interface is active, we keep the timestamping counter enabled * with fine resolution and binary rollover. This avoid non-monotonic behavior * (clock jumps) when changing timestamping settings at runtime. -@@ -7908,6 +7911,16 @@ int stmmac_resume(struct device *dev) +@@ -7909,6 +7912,16 @@ int stmmac_resume(struct device *dev) stmmac_free_tx_skbufs(priv); stmmac_clear_descriptors(priv, &priv->dma_conf);