diff options
author | John Crispin <john@phrozen.org> | 2016-08-11 16:34:08 +0200 |
---|---|---|
committer | John Crispin <john@phrozen.org> | 2016-08-18 09:49:18 +0200 |
commit | d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda (patch) | |
tree | 7808c469b98212bd9da8b8aa7132829946664a3c | |
parent | 5e563262f12d919f5062b5e47d60d0be64d33d35 (diff) | |
download | openwrt-d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda.tar.gz openwrt-d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda.tar.bz2 openwrt-d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda.zip |
ipq806x: sync with latest patches sent by QCA
Signed-off-by: John Crispin <john@phrozen.org>
11 files changed, 1350 insertions, 93 deletions
diff --git a/target/linux/ipq806x/modules.mk b/target/linux/ipq806x/modules.mk index 04a2d4fe2c..38dd195902 100644 --- a/target/linux/ipq806x/modules.mk +++ b/target/linux/ipq806x/modules.mk @@ -1,6 +1,22 @@ +define KernelPackage/usb-dwc3-of-simple + TITLE:=DWC3 USB simple OF driver + DEPENDS:=+kmod-usb-dwc3 + KCONFIG:= CONFIG_USB_DWC3_OF_SIMPLE + FILES:= $(LINUX_DIR)/drivers/usb/dwc3/dwc3-of-simple.ko + AUTOLOAD:=$(call AutoLoad,53,dwc3-of-simple,1) + $(call AddDepends/usb) +endef + +define KernelPackage/usb-dwc3-of-simple/description + This driver provides generic platform glue for the integrated DesignWare + USB3 IP Core. +endef + +$(eval $(call KernelPackage,usb-dwc3-of-simple)) + define KernelPackage/usb-phy-qcom-dwc3 TITLE:=DWC3 USB QCOM PHY driver - DEPENDS:=@TARGET_ipq806x + DEPENDS:=@TARGET_ipq806x +kmod-usb-dwc3-of-simple KCONFIG:= CONFIG_PHY_QCOM_DWC3 FILES:= $(LINUX_DIR)/drivers/phy/phy-qcom-dwc3.ko AUTOLOAD:=$(call AutoLoad,45,phy-qcom-dwc3,1) diff --git a/target/linux/ipq806x/patches-4.4/097-usb-dwc3-add-generic-OF-glue-layer.patch b/target/linux/ipq806x/patches-4.4/097-usb-dwc3-add-generic-OF-glue-layer.patch new file mode 100644 index 0000000000..96e9859060 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/097-usb-dwc3-add-generic-OF-glue-layer.patch @@ -0,0 +1,244 @@ +From 41c2b5280cd2fa3e198c422cdf223ba6e48f857a Mon Sep 17 00:00:00 2001 +From: Felipe Balbi <balbi@ti.com> +Date: Wed, 18 Nov 2015 13:15:20 -0600 +Subject: [PATCH] usb: dwc3: add generic OF glue layer + +For simple platforms which merely enable some clocks +and populate its children, we can use this generic +glue layer to avoid boilerplate code duplication. + +For now this supports Qcom and Xilinx, but if we +find a way to add generic handling of regulators and +optional PHYs, we can absorb exynos as well. + +Tested-by: Subbaraya Sundeep Bhatta <subbaraya.sundeep.bhatta@xilinx.com> +Signed-off-by: Felipe Balbi <balbi@ti.com> +(cherry picked from commit 16adc674d0d68a50dfc725574738d7ae11cf5d7e) + +Change-Id: I6fd260442997b198dc12ca726814b7a9518e6353 +Signed-off-by: Nitheesh Sekar <nsekar@codeaurora.org> +--- + drivers/usb/dwc3/Kconfig | 9 ++ + drivers/usb/dwc3/Makefile | 1 + + drivers/usb/dwc3/dwc3-of-simple.c | 178 ++++++++++++++++++++++++++++++++++++++ + 3 files changed, 188 insertions(+) + create mode 100644 drivers/usb/dwc3/dwc3-of-simple.c + +diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig +index 5a42c45..070e704 100644 +--- a/drivers/usb/dwc3/Kconfig ++++ b/drivers/usb/dwc3/Kconfig +@@ -87,6 +87,15 @@ config USB_DWC3_KEYSTONE + Support of USB2/3 functionality in TI Keystone2 platforms. + Say 'Y' or 'M' here if you have one such device + ++config USB_DWC3_OF_SIMPLE ++ tristate "Generic OF Simple Glue Layer" ++ depends on OF && COMMON_CLK ++ default USB_DWC3 ++ help ++ Support USB2/3 functionality in simple SoC integrations. ++ Currently supports Xilinx and Qualcomm DWC USB3 IP. ++ Say 'Y' or 'M' if you have one such device. ++ + config USB_DWC3_ST + tristate "STMicroelectronics Platforms" + depends on ARCH_STI && OF +diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile +index acc951d..6491f9b 100644 +--- a/drivers/usb/dwc3/Makefile ++++ b/drivers/usb/dwc3/Makefile +@@ -37,5 +37,6 @@ obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o + obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o + obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o + obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o ++obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o + obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o + obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o +diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c +new file mode 100644 +index 0000000..60c4c5a +--- /dev/null ++++ b/drivers/usb/dwc3/dwc3-of-simple.c +@@ -0,0 +1,178 @@ ++/** ++ * dwc3-of-simple.c - OF glue layer for simple integrations ++ * ++ * Copyright (c) 2015 Texas Instruments Incorporated - http://www.ti.com ++ * ++ * Author: Felipe Balbi <balbi@ti.com> ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License version 2 of ++ * the License as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov ++ * <iivanov@mm-sol.com> and the original patch adding support for Xilinx' SoC ++ * by Subbaraya Sundeep Bhatta <subbaraya.sundeep.bhatta@xilinx.com> ++ */ ++ ++#include <linux/module.h> ++#include <linux/kernel.h> ++#include <linux/slab.h> ++#include <linux/platform_device.h> ++#include <linux/dma-mapping.h> ++#include <linux/clk.h> ++#include <linux/clk-provider.h> ++#include <linux/of.h> ++#include <linux/of_platform.h> ++#include <linux/pm_runtime.h> ++ ++struct dwc3_of_simple { ++ struct device *dev; ++ struct clk **clks; ++ int num_clocks; ++}; ++ ++static int dwc3_of_simple_probe(struct platform_device *pdev) ++{ ++ struct dwc3_of_simple *simple; ++ struct device *dev = &pdev->dev; ++ struct device_node *np = dev->of_node; ++ ++ int ret; ++ int i; ++ ++ simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL); ++ if (!simple) ++ return -ENOMEM; ++ ++ ret = of_clk_get_parent_count(np); ++ if (ret < 0) ++ return ret; ++ ++ simple->num_clocks = ret; ++ ++ simple->clks = devm_kcalloc(dev, simple->num_clocks, ++ sizeof(struct clk *), GFP_KERNEL); ++ if (!simple->clks) ++ return -ENOMEM; ++ ++ simple->dev = dev; ++ ++ for (i = 0; i < simple->num_clocks; i++) { ++ struct clk *clk; ++ ++ clk = of_clk_get(np, i); ++ if (IS_ERR(clk)) { ++ while (--i >= 0) ++ clk_put(simple->clks[i]); ++ return PTR_ERR(clk); ++ } ++ ++ ret = clk_prepare_enable(clk); ++ if (ret < 0) { ++ while (--i >= 0) { ++ clk_disable_unprepare(simple->clks[i]); ++ clk_put(simple->clks[i]); ++ } ++ clk_put(clk); ++ ++ return ret; ++ } ++ ++ simple->clks[i] = clk; ++ } ++ ++ ret = of_platform_populate(np, NULL, NULL, dev); ++ if (ret) { ++ for (i = 0; i < simple->num_clocks; i++) { ++ clk_disable_unprepare(simple->clks[i]); ++ clk_put(simple->clks[i]); ++ } ++ ++ return ret; ++ } ++ ++ pm_runtime_set_active(dev); ++ pm_runtime_enable(dev); ++ pm_runtime_get_sync(dev); ++ ++ return 0; ++} ++ ++static int dwc3_of_simple_remove(struct platform_device *pdev) ++{ ++ struct dwc3_of_simple *simple = platform_get_drvdata(pdev); ++ struct device *dev = &pdev->dev; ++ int i; ++ ++ for (i = 0; i < simple->num_clocks; i++) { ++ clk_unprepare(simple->clks[i]); ++ clk_put(simple->clks[i]); ++ } ++ ++ of_platform_depopulate(dev); ++ ++ pm_runtime_put_sync(dev); ++ pm_runtime_disable(dev); ++ ++ return 0; ++} ++ ++static int dwc3_of_simple_runtime_suspend(struct device *dev) ++{ ++ struct dwc3_of_simple *simple = dev_get_drvdata(dev); ++ int i; ++ ++ for (i = 0; i < simple->num_clocks; i++) ++ clk_disable(simple->clks[i]); ++ ++ return 0; ++} ++ ++static int dwc3_of_simple_runtime_resume(struct device *dev) ++{ ++ struct dwc3_of_simple *simple = dev_get_drvdata(dev); ++ int ret; ++ int i; ++ ++ for (i = 0; i < simple->num_clocks; i++) { ++ ret = clk_enable(simple->clks[i]); ++ if (ret < 0) { ++ while (--i >= 0) ++ clk_disable(simple->clks[i]); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { ++ SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend, ++ dwc3_of_simple_runtime_resume, NULL) ++}; ++ ++static const struct of_device_id of_dwc3_simple_match[] = { ++ { .compatible = "qcom,dwc3" }, ++ { .compatible = "xlnx,zynqmp-dwc3" }, ++ { /* Sentinel */ } ++}; ++MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); ++ ++static struct platform_driver dwc3_of_simple_driver = { ++ .probe = dwc3_of_simple_probe, ++ .remove = dwc3_of_simple_remove, ++ .driver = { ++ .name = "dwc3-of-simple", ++ .of_match_table = of_dwc3_simple_match, ++ }, ++}; ++ ++module_platform_driver(dwc3_of_simple_driver); ++MODULE_LICENSE("GPL v2"); ++MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer"); ++MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/098-usb-dwc3-of-simple-fix-build-warning-on-PM.patch b/target/linux/ipq806x/patches-4.4/098-usb-dwc3-of-simple-fix-build-warning-on-PM.patch new file mode 100644 index 0000000000..2fbdd1b4be --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/098-usb-dwc3-of-simple-fix-build-warning-on-PM.patch @@ -0,0 +1,41 @@ +From 131386d63ca3177d471aa93808c69b85fdac520d Mon Sep 17 00:00:00 2001 +From: Felipe Balbi <balbi@ti.com> +Date: Tue, 22 Dec 2015 21:56:10 -0600 +Subject: [PATCH] usb: dwc3: of-simple: fix build warning on !PM + +if we have a !PM kernel build, our runtime +suspend/resume callbacks will be left defined but +unused. Add a ifdef CONFIG_PM guard. + +Signed-off-by: Felipe Balbi <balbi@ti.com> +(cherry picked from commit 5072cfc40a80cea3749fd3413b3896630d8c787e) + +Change-Id: I088186c33aa917ec8da2985372ceefc289b24242 +Signed-off-by: Nitheesh Sekar <nsekar@codeaurora.org> +--- + drivers/usb/dwc3/dwc3-of-simple.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c +index 60c4c5a..9c9f741 100644 +--- a/drivers/usb/dwc3/dwc3-of-simple.c ++++ b/drivers/usb/dwc3/dwc3-of-simple.c +@@ -122,6 +122,7 @@ static int dwc3_of_simple_remove(struct platform_device *pdev) + return 0; + } + ++#ifdef CONFIG_PM + static int dwc3_of_simple_runtime_suspend(struct device *dev) + { + struct dwc3_of_simple *simple = dev_get_drvdata(dev); +@@ -150,6 +151,7 @@ static int dwc3_of_simple_runtime_resume(struct device *dev) + + return 0; + } ++#endif + + static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = { + SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend, +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/099-usb-dwc3-Remove-impossible-check-for-of_clk_get_pare.patch b/target/linux/ipq806x/patches-4.4/099-usb-dwc3-Remove-impossible-check-for-of_clk_get_pare.patch new file mode 100644 index 0000000000..44506c1256 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/099-usb-dwc3-Remove-impossible-check-for-of_clk_get_pare.patch @@ -0,0 +1,52 @@ +From 07c8b15688055d81ac8e1c8c964b9e4c302287f1 Mon Sep 17 00:00:00 2001 +From: Stephen Boyd <sboyd@codeaurora.org> +Date: Mon, 22 Feb 2016 11:12:47 -0800 +Subject: [PATCH] usb: dwc3: Remove impossible check for + of_clk_get_parent_count() < 0 + +The check for < 0 is impossible now that +of_clk_get_parent_count() returns an unsigned int. Simplify the +code and update the types. + +Acked-by: Felipe Balbi <balbi@kernel.org> +Cc: <linux-usb@vger.kernel.org> +Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> +(cherry picked from commit 3d755dcc20dd452b52532eca17da40ebbd12aee9) + +Change-Id: Iaa38e064d801fb36c855fea51c0443840368e0d3 +Signed-off-by: Nitheesh Sekar <nsekar@codeaurora.org> +--- + drivers/usb/dwc3/dwc3-of-simple.c | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c +index 9c9f741..9743353 100644 +--- a/drivers/usb/dwc3/dwc3-of-simple.c ++++ b/drivers/usb/dwc3/dwc3-of-simple.c +@@ -42,6 +42,7 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + ++ unsigned int count; + int ret; + int i; + +@@ -49,11 +50,11 @@ static int dwc3_of_simple_probe(struct platform_device *pdev) + if (!simple) + return -ENOMEM; + +- ret = of_clk_get_parent_count(np); +- if (ret < 0) +- return ret; ++ count = of_clk_get_parent_count(np); ++ if (!count) ++ return -ENOENT; + +- simple->num_clocks = ret; ++ simple->num_clocks = count; + + simple->clks = devm_kcalloc(dev, simple->num_clocks, + sizeof(struct clk *), GFP_KERNEL); +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch b/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch index 4bba0ac557..d5549191e2 100644 --- a/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch +++ b/target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch @@ -1,9 +1,9 @@ --- a/drivers/phy/Kconfig +++ b/drivers/phy/Kconfig -@@ -390,4 +390,15 @@ config PHY_CYGNUS_PCIE - Enable this to support the Broadcom Cygnus PCIe PHY. - If unsure, say N. - +@@ -390,4 +390,15 @@ + Enable this to support the Broadcom Cygnus PCIe PHY. + If unsure, say N. + +config PHY_QCOM_DWC3 + tristate "QCOM DWC3 USB PHY support" + depends on ARCH_QCOM @@ -18,25 +18,25 @@ endmenu --- a/drivers/phy/Makefile +++ b/drivers/phy/Makefile -@@ -48,3 +48,4 @@ obj-$(CONFIG_PHY_TUSB1210) += phy-tusb1 +@@ -48,3 +48,4 @@ obj-$(CONFIG_PHY_TUSB1210) += obj-$(CONFIG_PHY_BRCMSTB_SATA) += phy-brcmstb-sata.o obj-$(CONFIG_PHY_PISTACHIO_USB) += phy-pistachio-usb.o obj-$(CONFIG_PHY_CYGNUS_PCIE) += phy-bcm-cygnus-pcie.o +obj-$(CONFIG_PHY_QCOM_DWC3) += phy-qcom-dwc3.o --- /dev/null +++ b/drivers/phy/phy-qcom-dwc3.c -@@ -0,0 +1,482 @@ -+/* Copyright (c) 2013-2014, Code Aurora Forum. All rights reserved. +@@ -0,0 +1,484 @@ ++/* Copyright (c) 2014-2015, Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * -+ * This program is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ */ ++* 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 <linux/clk.h> +#include <linux/err.h> @@ -57,7 +57,7 @@ +#define HSUSB_CTRL_DMSEHV_CLAMP BIT(24) +#define HSUSB_CTRL_USB2_SUSPEND BIT(23) +#define HSUSB_CTRL_UTMI_CLK_EN BIT(21) -+#define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20) ++#define HSUSB_CTRL_UTMI_OTG_VBUS_VALID BIT(20) +#define HSUSB_CTRL_USE_CLKCORE BIT(18) +#define HSUSB_CTRL_DPSEHV_CLAMP BIT(17) +#define HSUSB_CTRL_COMMONONN BIT(11) @@ -113,18 +113,24 @@ +#define TX_OVRD_DRV_LO_PREEMPH_SHIFT 7 +#define TX_OVRD_DRV_LO_EN BIT(14) + ++/* SS CAP register bits */ ++#define SS_CR_CAP_ADDR_REG BIT(0) ++#define SS_CR_CAP_DATA_REG BIT(0) ++#define SS_CR_READ_REG BIT(0) ++#define SS_CR_WRITE_REG BIT(0) ++ +struct qcom_dwc3_usb_phy { + void __iomem *base; + struct device *dev; -+ struct phy *phy; -+ -+ int (*phy_init)(struct qcom_dwc3_usb_phy *phy_dwc3); -+ int (*phy_exit)(struct qcom_dwc3_usb_phy *phy_dwc3); -+ + struct clk *xo_clk; + struct clk *ref_clk; +}; + ++struct qcom_dwc3_phy_drvdata { ++ struct phy_ops ops; ++ u32 clk_rate; ++}; ++ +/** + * Write register and read back masked value to confirm it is written + * @@ -177,29 +183,32 @@ + * @addr - SSPHY address to write. + * @val - value to write. + */ -+static int qcom_dwc3_ss_write_phycreg(void __iomem *base, u32 addr, u32 val) ++static int qcom_dwc3_ss_write_phycreg(struct qcom_dwc3_usb_phy *phy_dwc3, ++ u32 addr, u32 val) +{ + int ret; + -+ writel(addr, base + CR_PROTOCOL_DATA_IN_REG); -+ writel(0x1, base + CR_PROTOCOL_CAP_ADDR_REG); ++ writel(addr, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG); ++ writel(SS_CR_CAP_ADDR_REG, phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG); + -+ ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG); ++ ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_ADDR_REG); + if (ret) + goto err_wait; + -+ writel(val, base + CR_PROTOCOL_DATA_IN_REG); -+ writel(0x1, base + CR_PROTOCOL_CAP_DATA_REG); ++ writel(val, phy_dwc3->base + CR_PROTOCOL_DATA_IN_REG); ++ writel(SS_CR_CAP_DATA_REG, phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG); + -+ ret = wait_for_latch(base + CR_PROTOCOL_CAP_DATA_REG); ++ ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_CAP_DATA_REG); + if (ret) + goto err_wait; + -+ writel(0x1, base + CR_PROTOCOL_WRITE_REG); ++ writel(SS_CR_WRITE_REG, phy_dwc3->base + CR_PROTOCOL_WRITE_REG); + -+ ret = wait_for_latch(base + CR_PROTOCOL_WRITE_REG); ++ ret = wait_for_latch(phy_dwc3->base + CR_PROTOCOL_WRITE_REG); + +err_wait: ++ if (ret) ++ dev_err(phy_dwc3->dev, "timeout waiting for latch\n"); + return ret; +} + @@ -212,10 +221,9 @@ +static int qcom_dwc3_ss_read_phycreg(void __iomem *base, u32 addr, u32 *val) +{ + int ret; -+ bool first_read = true; + + writel(addr, base + CR_PROTOCOL_DATA_IN_REG); -+ writel(0x1, base + CR_PROTOCOL_CAP_ADDR_REG); ++ writel(SS_CR_CAP_ADDR_REG, base + CR_PROTOCOL_CAP_ADDR_REG); + + ret = wait_for_latch(base + CR_PROTOCOL_CAP_ADDR_REG); + if (ret) @@ -226,18 +234,20 @@ + * incorrect. Hence as workaround, SW should perform SSPHY register + * read twice, but use only second read and ignore first read. + */ -+retry: -+ writel(0x1, base + CR_PROTOCOL_READ_REG); ++ writel(SS_CR_READ_REG, base + CR_PROTOCOL_READ_REG); + + ret = wait_for_latch(base + CR_PROTOCOL_READ_REG); + if (ret) + goto err_wait; + -+ if (first_read) { -+ readl(base + CR_PROTOCOL_DATA_OUT_REG); -+ first_read = false; -+ goto retry; -+ } ++ /* throwaway read */ ++ readl(base + CR_PROTOCOL_DATA_OUT_REG); ++ ++ writel(SS_CR_READ_REG, base + CR_PROTOCOL_READ_REG); ++ ++ ret = wait_for_latch(base + CR_PROTOCOL_READ_REG); ++ if (ret) ++ goto err_wait; + + *val = readl(base + CR_PROTOCOL_DATA_OUT_REG); + @@ -271,8 +281,9 @@ + return 0; +} + -+static int qcom_dwc3_hs_phy_init(struct qcom_dwc3_usb_phy *phy_dwc3) ++static int qcom_dwc3_hs_phy_init(struct phy *phy) +{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); + u32 val; + + /* @@ -298,17 +309,18 @@ + return 0; +} + -+static int qcom_dwc3_ss_phy_init(struct qcom_dwc3_usb_phy *phy_dwc3) ++static int qcom_dwc3_ss_phy_init(struct phy *phy) +{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); + int ret; + u32 data = 0; + + /* reset phy */ -+ data = readl_relaxed(phy_dwc3->base + SSUSB_PHY_CTRL_REG); -+ writel_relaxed(data | SSUSB_CTRL_SS_PHY_RESET, ++ data = readl(phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ writel(data | SSUSB_CTRL_SS_PHY_RESET, + phy_dwc3->base + SSUSB_PHY_CTRL_REG); + usleep_range(2000, 2200); -+ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); + + /* clear REF_PAD if we don't have XO clk */ + if (!phy_dwc3->xo_clk) @@ -316,11 +328,13 @@ + else + data |= SSUSB_CTRL_REF_USE_PAD; + -+ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ ++ /* wait for ref clk to become stable, this can take up to 30ms */ + msleep(30); + + data |= SSUSB_CTRL_SS_PHY_EN | SSUSB_CTRL_LANE0_PWR_PRESENT; -+ writel_relaxed(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); ++ writel(data, phy_dwc3->base + SSUSB_PHY_CTRL_REG); + + /* + * Fix RX Equalization setting as follows @@ -339,7 +353,7 @@ + data &= ~RX_OVRD_IN_HI_RX_EQ_MASK; + data |= 0x3 << RX_OVRD_IN_HI_RX_EQ_SHIFT; + data |= RX_OVRD_IN_HI_RX_EQ_OVRD; -+ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3->base, ++ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3, + SSPHY_CTRL_RX_OVRD_IN_HI(0), data); + if (ret) + goto err_phy_trans; @@ -360,7 +374,7 @@ + data &= ~TX_OVRD_DRV_LO_AMPLITUDE_MASK; + data |= 0x7f; + data |= TX_OVRD_DRV_LO_EN; -+ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3->base, ++ ret = qcom_dwc3_ss_write_phycreg(phy_dwc3, + SSPHY_CTRL_TX_OVRD_DRV_LO(0), data); + if (ret) + goto err_phy_trans; @@ -378,13 +392,14 @@ + return ret; +} + -+static int qcom_dwc3_ss_phy_exit(struct qcom_dwc3_usb_phy *phy_dwc3) ++static int qcom_dwc3_ss_phy_exit(struct phy *phy) +{ ++ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); ++ + /* Sequence to put SSPHY in low power state: + * 1. Clear REF_PHY_EN in PHY_CTRL_REG + * 2. Clear REF_USE_PAD in PHY_CTRL_REG + * 3. Set TEST_POWERED_DOWN in PHY_CTRL_REG to enable PHY retention -+ * 4. Disable SSPHY ref clk + */ + qcom_dwc3_phy_write_readback(phy_dwc3, SSUSB_PHY_CTRL_REG, + SSUSB_CTRL_SS_PHY_EN, 0x0); @@ -396,37 +411,30 @@ + return 0; +} + -+static int qcom_dwc3_phy_init(struct phy *phy) -+{ -+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); -+ -+ if (phy_dwc3->phy_init) -+ return phy_dwc3->phy_init(phy_dwc3); -+ -+ return 0; -+} -+ -+static int qcom_dwc3_phy_exit(struct phy *phy) -+{ -+ struct qcom_dwc3_usb_phy *phy_dwc3 = phy_get_drvdata(phy); -+ -+ if (phy_dwc3->phy_exit) -+ return qcom_dwc3_ss_phy_exit(phy_dwc3); -+ -+ return 0; -+} ++static const struct qcom_dwc3_phy_drvdata qcom_dwc3_hs_drvdata = { ++ .ops = { ++ .init = qcom_dwc3_hs_phy_init, ++ .power_on = qcom_dwc3_phy_power_on, ++ .power_off = qcom_dwc3_phy_power_off, ++ .owner = THIS_MODULE, ++ }, ++ .clk_rate = 60000000, ++}; + -+static struct phy_ops qcom_dwc3_phy_ops = { -+ .init = qcom_dwc3_phy_init, -+ .exit = qcom_dwc3_phy_exit, -+ .power_on = qcom_dwc3_phy_power_on, -+ .power_off = qcom_dwc3_phy_power_off, -+ .owner = THIS_MODULE, ++static const struct qcom_dwc3_phy_drvdata qcom_dwc3_ss_drvdata = { ++ .ops = { ++ .init = qcom_dwc3_ss_phy_init, ++ .exit = qcom_dwc3_ss_phy_exit, ++ .power_on = qcom_dwc3_phy_power_on, ++ .power_off = qcom_dwc3_phy_power_off, ++ .owner = THIS_MODULE, ++ }, ++ .clk_rate = 125000000, +}; + +static const struct of_device_id qcom_dwc3_phy_table[] = { -+ { .compatible = "qcom,dwc3-hs-usb-phy", }, -+ { .compatible = "qcom,dwc3-ss-usb-phy", }, ++ { .compatible = "qcom,dwc3-hs-usb-phy", .data = &qcom_dwc3_hs_drvdata }, ++ { .compatible = "qcom,dwc3-ss-usb-phy", .data = &qcom_dwc3_ss_drvdata }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, qcom_dwc3_phy_table); @@ -435,13 +443,17 @@ +{ + struct qcom_dwc3_usb_phy *phy_dwc3; + struct phy_provider *phy_provider; ++ struct phy *generic_phy; + struct resource *res; ++ const struct of_device_id *match; ++ const struct qcom_dwc3_phy_drvdata *data; + + phy_dwc3 = devm_kzalloc(&pdev->dev, sizeof(*phy_dwc3), GFP_KERNEL); + if (!phy_dwc3) + return -ENOMEM; + -+ platform_set_drvdata(pdev, phy_dwc3); ++ match = of_match_node(qcom_dwc3_phy_table, pdev->dev.of_node); ++ data = match->data; + + phy_dwc3->dev = &pdev->dev; + @@ -456,19 +468,7 @@ + return PTR_ERR(phy_dwc3->ref_clk); + } + -+ if (of_device_is_compatible(pdev->dev.of_node, -+ "qcom,dwc3-hs-usb-phy")) { -+ clk_set_rate(phy_dwc3->ref_clk, 60000000); -+ phy_dwc3->phy_init = qcom_dwc3_hs_phy_init; -+ } else if (of_device_is_compatible(pdev->dev.of_node, -+ "qcom,dwc3-ss-usb-phy")) { -+ phy_dwc3->phy_init = qcom_dwc3_ss_phy_init; -+ phy_dwc3->phy_exit = qcom_dwc3_ss_phy_exit; -+ clk_set_rate(phy_dwc3->ref_clk, 125000000); -+ } else { -+ dev_err(phy_dwc3->dev, "Unknown phy\n"); -+ return -EINVAL; -+ } ++ clk_set_rate(phy_dwc3->ref_clk, data->clk_rate); + + phy_dwc3->xo_clk = devm_clk_get(phy_dwc3->dev, "xo"); + if (IS_ERR(phy_dwc3->xo_clk)) { @@ -476,12 +476,14 @@ + phy_dwc3->xo_clk = NULL; + } + -+ phy_dwc3->phy = devm_phy_create(phy_dwc3->dev, NULL, &qcom_dwc3_phy_ops); ++ generic_phy = devm_phy_create(phy_dwc3->dev, pdev->dev.of_node, ++ &data->ops); + -+ if (IS_ERR(phy_dwc3->phy)) -+ return PTR_ERR(phy_dwc3->phy); ++ if (IS_ERR(generic_phy)) ++ return PTR_ERR(generic_phy); + -+ phy_set_drvdata(phy_dwc3->phy, phy_dwc3); ++ phy_set_drvdata(generic_phy, phy_dwc3); ++ platform_set_drvdata(pdev, phy_dwc3); + + phy_provider = devm_of_phy_provider_register(phy_dwc3->dev, + of_phy_simple_xlate); diff --git a/target/linux/ipq806x/patches-4.4/710-spi-qup-Make-sure-mode-is-only-determined-once.patch b/target/linux/ipq806x/patches-4.4/710-spi-qup-Make-sure-mode-is-only-determined-once.patch new file mode 100644 index 0000000000..e5dafd76b4 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/710-spi-qup-Make-sure-mode-is-only-determined-once.patch @@ -0,0 +1,225 @@ +From 93f99afbc534e00d72d58336061823055ee820f1 Mon Sep 17 00:00:00 2001 +From: Andy Gross <andy.gross@linaro.org> +Date: Tue, 12 Apr 2016 09:11:47 -0500 +Subject: [PATCH] spi: qup: Make sure mode is only determined once + +This patch calculates the mode once. All decisions on the current +transaction +is made using the mode instead of use_dma + +Signed-off-by: Andy Gross <andy.gross@linaro.org> + +Change-Id: If3cdd924355e037d77dc8201a72895fac0461aa5 +--- + drivers/spi/spi-qup.c | 96 +++++++++++++++++++-------------------------------- + 1 file changed, 36 insertions(+), 60 deletions(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index eb2cb8c..714fd4e 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -150,13 +150,20 @@ struct spi_qup { + int rx_bytes; + int qup_v1; + +- int use_dma; ++ int mode; + struct dma_slave_config rx_conf; + struct dma_slave_config tx_conf; +- int mode; + }; + + ++static inline bool spi_qup_is_dma_xfer(int mode) ++{ ++ if (mode == QUP_IO_M_MODE_DMOV || mode == QUP_IO_M_MODE_BAM) ++ return true; ++ ++ return false; ++} ++ + static inline bool spi_qup_is_valid_state(struct spi_qup *controller) + { + u32 opstate = readl_relaxed(controller->base + QUP_STATE); +@@ -427,7 +434,7 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + error = -EIO; + } + +- if (!controller->use_dma) { ++ if (!spi_qup_is_dma_xfer(controller->mode)) { + if (opflags & QUP_OP_IN_SERVICE_FLAG) + spi_qup_fifo_read(controller, xfer); + +@@ -446,43 +453,11 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + return IRQ_HANDLED; + } + +-static u32 +-spi_qup_get_mode(struct spi_master *master, struct spi_transfer *xfer) +-{ +- struct spi_qup *qup = spi_master_get_devdata(master); +- u32 mode; +- size_t dma_align = dma_get_cache_alignment(); +- +- qup->w_size = 4; +- +- if (xfer->bits_per_word <= 8) +- qup->w_size = 1; +- else if (xfer->bits_per_word <= 16) +- qup->w_size = 2; +- +- qup->n_words = xfer->len / qup->w_size; +- +- if (!IS_ERR_OR_NULL(master->dma_rx) && +- IS_ALIGNED((size_t)xfer->tx_buf, dma_align) && +- IS_ALIGNED((size_t)xfer->rx_buf, dma_align) && +- !is_vmalloc_addr(xfer->tx_buf) && +- !is_vmalloc_addr(xfer->rx_buf) && +- (xfer->len > 3*qup->in_blk_sz)) +- qup->use_dma = 1; +- +- if (qup->n_words <= (qup->in_fifo_sz / sizeof(u32))) +- mode = QUP_IO_M_MODE_FIFO; +- else +- mode = QUP_IO_M_MODE_BLOCK; +- +- return mode; +-} +- + /* set clock freq ... bits per word */ + static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + { + struct spi_qup *controller = spi_master_get_devdata(spi->master); +- u32 config, iomode, mode, control; ++ u32 config, iomode, control; + int ret, n_words; + + if (spi->mode & SPI_LOOP && xfer->len > controller->in_fifo_sz) { +@@ -503,24 +478,22 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + return -EIO; + } + +- controller->mode = mode = spi_qup_get_mode(spi->master, xfer); ++ controller->w_size = DIV_ROUND_UP(xfer->bits_per_word, 8); ++ controller->n_words = xfer->len / controller->w_size; + n_words = controller->n_words; + +- if (mode == QUP_IO_M_MODE_FIFO) { ++ if (n_words <= (controller->in_fifo_sz / sizeof(u32))) { ++ controller->mode = QUP_IO_M_MODE_FIFO; + writel_relaxed(n_words, controller->base + QUP_MX_READ_CNT); + writel_relaxed(n_words, controller->base + QUP_MX_WRITE_CNT); + /* must be zero for FIFO */ + writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); + controller->use_dma = 0; +- } else if (!controller->use_dma) { +- writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); +- writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); +- /* must be zero for BLOCK and BAM */ +- writel_relaxed(0, controller->base + QUP_MX_READ_CNT); +- writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); +- } else { +- mode = QUP_IO_M_MODE_BAM; ++ } else if (spi->master->can_dma && ++ spi->master->can_dma(spi->master, spi, xfer) && ++ spi->master->cur_msg_mapped) { ++ controller->mode = QUP_IO_M_MODE_BAM; + writel_relaxed(0, controller->base + QUP_MX_READ_CNT); + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + +@@ -541,19 +514,26 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); + } ++ } else { ++ controller->mode = QUP_IO_M_MODE_BLOCK; ++ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); ++ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); ++ /* must be zero for BLOCK and BAM */ ++ writel_relaxed(0, controller->base + QUP_MX_READ_CNT); ++ writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + } + + iomode = readl_relaxed(controller->base + QUP_IO_M_MODES); + /* Set input and output transfer mode */ + iomode &= ~(QUP_IO_M_INPUT_MODE_MASK | QUP_IO_M_OUTPUT_MODE_MASK); + +- if (!controller->use_dma) ++ if (!spi_qup_is_dma_xfer(controller->mode)) + iomode &= ~(QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN); + else + iomode |= QUP_IO_M_PACK_EN | QUP_IO_M_UNPACK_EN; + +- iomode |= (mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); +- iomode |= (mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); ++ iomode |= (controller->mode << QUP_IO_M_OUTPUT_MODE_MASK_SHIFT); ++ iomode |= (controller->mode << QUP_IO_M_INPUT_MODE_MASK_SHIFT); + + writel_relaxed(iomode, controller->base + QUP_IO_M_MODES); + +@@ -594,7 +574,7 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + config |= xfer->bits_per_word - 1; + config |= QUP_CONFIG_SPI_MODE; + +- if (controller->use_dma) { ++ if (spi_qup_is_dma_xfer(controller->mode)) { + if (!xfer->tx_buf) + config |= QUP_CONFIG_NO_OUTPUT; + if (!xfer->rx_buf) +@@ -612,7 +592,7 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + * status change in BAM mode + */ + +- if (mode == QUP_IO_M_MODE_BAM) ++ if (spi_qup_is_dma_xfer(controller->mode)) + mask = QUP_OP_IN_SERVICE_FLAG | QUP_OP_OUT_SERVICE_FLAG; + + writel_relaxed(mask, controller->base + QUP_OPERATIONAL_MASK); +@@ -646,7 +626,7 @@ static int spi_qup_transfer_one(struct spi_master *master, + controller->tx_bytes = 0; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (controller->use_dma) ++ if (spi_qup_is_dma_xfer(controller->mode)) + ret = spi_qup_do_dma(master, xfer); + else + ret = spi_qup_do_pio(master, xfer); +@@ -670,7 +650,7 @@ exit: + ret = controller->error; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (ret && controller->use_dma) ++ if (ret && spi_qup_is_dma_xfer(controller->mode)) + spi_qup_dma_terminate(master, xfer); + + return ret; +@@ -681,9 +661,7 @@ static bool spi_qup_can_dma(struct spi_master *master, struct spi_device *spi, + { + struct spi_qup *qup = spi_master_get_devdata(master); + size_t dma_align = dma_get_cache_alignment(); +- u32 mode; +- +- qup->use_dma = 0; ++ int n_words; + + if (xfer->rx_buf && (xfer->len % qup->in_blk_sz || + IS_ERR_OR_NULL(master->dma_rx) || +@@ -695,12 +673,10 @@ static bool spi_qup_can_dma(struct spi_master *master, struct spi_device *spi, + !IS_ALIGNED((size_t)xfer->tx_buf, dma_align))) + return false; + +- mode = spi_qup_get_mode(master, xfer); +- if (mode == QUP_IO_M_MODE_FIFO) ++ n_words = xfer->len / DIV_ROUND_UP(xfer->bits_per_word, 8); ++ if (n_words <= (qup->in_fifo_sz / sizeof(u32))) + return false; + +- qup->use_dma = 1; +- + return true; + } + +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/711-spi-qup-Fix-transaction-done-signaling.patch b/target/linux/ipq806x/patches-4.4/711-spi-qup-Fix-transaction-done-signaling.patch new file mode 100644 index 0000000000..dd4bad344b --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/711-spi-qup-Fix-transaction-done-signaling.patch @@ -0,0 +1,35 @@ +From 8e830bd17e945e74964a5b61353d74e34c0791cd Mon Sep 17 00:00:00 2001 +From: Andy Gross <andy.gross@linaro.org> +Date: Fri, 29 Jan 2016 22:06:50 -0600 +Subject: [PATCH] spi: qup: Fix transaction done signaling + +Wait to signal done until we get all of the interrupts we are expecting +to get for a transaction. If we don't wait for the input done flag, we +can be inbetween transactions when the done flag comes in and this can +mess up the next transaction. + +Change-Id: I08d78376e71590663158d6434a3fb7c0623264c9 +CC: Grant Grundler <grundler@chromium.org> +CC: Sarthak Kukreti <skukreti@codeaurora.org> +Signed-off-by: Andy Gross <andy.gross@linaro.org> +--- + drivers/spi/spi-qup.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index 714fd4e..fe629f2 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -447,7 +447,8 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + controller->xfer = xfer; + spin_unlock_irqrestore(&controller->lock, flags); + +- if (controller->rx_bytes == xfer->len || error) ++ if ((controller->rx_bytes == xfer->len && ++ (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error) + complete(&controller->done); + + return IRQ_HANDLED; +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/712-spi-qup-Fix-DMA-mode-to-work-correctly.patch b/target/linux/ipq806x/patches-4.4/712-spi-qup-Fix-DMA-mode-to-work-correctly.patch new file mode 100644 index 0000000000..d9abc65ce2 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/712-spi-qup-Fix-DMA-mode-to-work-correctly.patch @@ -0,0 +1,226 @@ +From ed56e6322b067a898a25bda1774eb1180a832246 Mon Sep 17 00:00:00 2001 +From: Andy Gross <andy.gross@linaro.org> +Date: Tue, 2 Feb 2016 17:00:53 -0600 +Subject: [PATCH] spi: qup: Fix DMA mode to work correctly + +This patch fixes a few issues with the DMA mode. The QUP needs to be +placed in the run mode before the DMA transactions are executed. The +conditions for being able to DMA vary between revisions of the QUP. +This is due to v1.1.1 using ADM DMA and later revisions using BAM. + +Change-Id: Ib1f876eaa05d079e0bca4358d2b25b2940986089 +Signed-off-by: Andy Gross <andy.gross@linaro.org> +--- + drivers/spi/spi-qup.c | 95 ++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 63 insertions(+), 32 deletions(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index fe629f2..089c5e8 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -143,6 +143,7 @@ struct spi_qup { + + struct spi_transfer *xfer; + struct completion done; ++ struct completion dma_tx_done; + int error; + int w_size; /* bytes per SPI word */ + int n_words; +@@ -285,16 +286,16 @@ static void spi_qup_fifo_write(struct spi_qup *controller, + + static void spi_qup_dma_done(void *data) + { +- struct spi_qup *qup = data; ++ struct completion *done = data; + +- complete(&qup->done); ++ complete(done); + } + + static int spi_qup_prep_sg(struct spi_master *master, struct spi_transfer *xfer, + enum dma_transfer_direction dir, +- dma_async_tx_callback callback) ++ dma_async_tx_callback callback, ++ void *data) + { +- struct spi_qup *qup = spi_master_get_devdata(master); + unsigned long flags = DMA_PREP_INTERRUPT | DMA_PREP_FENCE; + struct dma_async_tx_descriptor *desc; + struct scatterlist *sgl; +@@ -313,11 +314,11 @@ static int spi_qup_prep_sg(struct spi_master *master, struct spi_transfer *xfer, + } + + desc = dmaengine_prep_slave_sg(chan, sgl, nents, dir, flags); +- if (!desc) +- return -EINVAL; ++ if (IS_ERR_OR_NULL(desc)) ++ return desc ? PTR_ERR(desc) : -EINVAL; + + desc->callback = callback; +- desc->callback_param = qup; ++ desc->callback_param = data; + + cookie = dmaengine_submit(desc); + +@@ -333,18 +334,29 @@ static void spi_qup_dma_terminate(struct spi_master *master, + dmaengine_terminate_all(master->dma_rx); + } + +-static int spi_qup_do_dma(struct spi_master *master, struct spi_transfer *xfer) ++static int spi_qup_do_dma(struct spi_master *master, struct spi_transfer *xfer, ++unsigned long timeout) + { ++ struct spi_qup *qup = spi_master_get_devdata(master); + dma_async_tx_callback rx_done = NULL, tx_done = NULL; + int ret; + ++ /* before issuing the descriptors, set the QUP to run */ ++ ret = spi_qup_set_state(qup, QUP_STATE_RUN); ++ if (ret) { ++ dev_warn(qup->dev, "cannot set RUN state\n"); ++ return ret; ++ } ++ + if (xfer->rx_buf) + rx_done = spi_qup_dma_done; +- else if (xfer->tx_buf) ++ ++ if (xfer->tx_buf) + tx_done = spi_qup_dma_done; + + if (xfer->rx_buf) { +- ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done); ++ ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done, ++ &qup->done); + if (ret) + return ret; + +@@ -352,17 +364,26 @@ static int spi_qup_do_dma(struct spi_master *master, struct spi_transfer *xfer) + } + + if (xfer->tx_buf) { +- ret = spi_qup_prep_sg(master, xfer, DMA_MEM_TO_DEV, tx_done); ++ ret = spi_qup_prep_sg(master, xfer, DMA_MEM_TO_DEV, tx_done, ++ &qup->dma_tx_done); + if (ret) + return ret; + + dma_async_issue_pending(master->dma_tx); + } + +- return 0; ++ if (xfer->rx_buf && !wait_for_completion_timeout(&qup->done, timeout)) ++ return -ETIMEDOUT; ++ ++ if (xfer->tx_buf && ++ !wait_for_completion_timeout(&qup->dma_tx_done, timeout)) ++ ret = -ETIMEDOUT; ++ ++ return ret; + } + +-static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer) ++static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer, ++ unsigned long timeout) + { + struct spi_qup *qup = spi_master_get_devdata(master); + int ret; +@@ -382,6 +403,15 @@ static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer) + if (qup->mode == QUP_IO_M_MODE_FIFO) + spi_qup_fifo_write(qup, xfer); + ++ ret = spi_qup_set_state(qup, QUP_STATE_RUN); ++ if (ret) { ++ dev_warn(qup->dev, "cannot set RUN state\n"); ++ return ret; ++ } ++ ++ if (!wait_for_completion_timeout(&qup->done, timeout)) ++ return -ETIMEDOUT; ++ + return 0; + } + +@@ -430,7 +460,6 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + dev_warn(controller->dev, "CLK_OVER_RUN\n"); + if (spi_err & SPI_ERROR_CLK_UNDER_RUN) + dev_warn(controller->dev, "CLK_UNDER_RUN\n"); +- + error = -EIO; + } + +@@ -619,6 +648,7 @@ static int spi_qup_transfer_one(struct spi_master *master, + timeout = 100 * msecs_to_jiffies(timeout); + + reinit_completion(&controller->done); ++ reinit_completion(&controller->dma_tx_done); + + spin_lock_irqsave(&controller->lock, flags); + controller->xfer = xfer; +@@ -628,21 +658,13 @@ static int spi_qup_transfer_one(struct spi_master *master, + spin_unlock_irqrestore(&controller->lock, flags); + + if (spi_qup_is_dma_xfer(controller->mode)) +- ret = spi_qup_do_dma(master, xfer); ++ ret = spi_qup_do_dma(master, xfer, timeout); + else +- ret = spi_qup_do_pio(master, xfer); ++ ret = spi_qup_do_pio(master, xfer, timeout); + + if (ret) + goto exit; + +- if (spi_qup_set_state(controller, QUP_STATE_RUN)) { +- dev_warn(controller->dev, "cannot set EXECUTE state\n"); +- goto exit; +- } +- +- if (!wait_for_completion_timeout(&controller->done, timeout)) +- ret = -ETIMEDOUT; +- + exit: + spi_qup_set_state(controller, QUP_STATE_RESET); + spin_lock_irqsave(&controller->lock, flags); +@@ -664,15 +686,23 @@ static bool spi_qup_can_dma(struct spi_master *master, struct spi_device *spi, + size_t dma_align = dma_get_cache_alignment(); + int n_words; + +- if (xfer->rx_buf && (xfer->len % qup->in_blk_sz || +- IS_ERR_OR_NULL(master->dma_rx) || +- !IS_ALIGNED((size_t)xfer->rx_buf, dma_align))) +- return false; ++ if (xfer->rx_buf) { ++ if (!IS_ALIGNED((size_t)xfer->rx_buf, dma_align) || ++ IS_ERR_OR_NULL(master->dma_rx)) ++ return false; + +- if (xfer->tx_buf && (xfer->len % qup->out_blk_sz || +- IS_ERR_OR_NULL(master->dma_tx) || +- !IS_ALIGNED((size_t)xfer->tx_buf, dma_align))) +- return false; ++ if (qup->qup_v1 && (xfer->len % qup->in_blk_sz)) ++ return false; ++ } ++ ++ if (xfer->tx_buf) { ++ if (!IS_ALIGNED((size_t)xfer->tx_buf, dma_align) || ++ IS_ERR_OR_NULL(master->dma_tx)) ++ return false; ++ ++ if (qup->qup_v1 && (xfer->len % qup->out_blk_sz)) ++ return false; ++ } + + n_words = xfer->len / DIV_ROUND_UP(xfer->bits_per_word, 8); + if (n_words <= (qup->in_fifo_sz / sizeof(u32))) +@@ -875,6 +905,7 @@ static int spi_qup_probe(struct platform_device *pdev) + + spin_lock_init(&controller->lock); + init_completion(&controller->done); ++ init_completion(&controller->dma_tx_done); + + iomode = readl_relaxed(base + QUP_IO_M_MODES); + +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/713-spi-qup-Fix-block-mode-to-work-correctly.patch b/target/linux/ipq806x/patches-4.4/713-spi-qup-Fix-block-mode-to-work-correctly.patch new file mode 100644 index 0000000000..7a05ecdba5 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/713-spi-qup-Fix-block-mode-to-work-correctly.patch @@ -0,0 +1,317 @@ +From 148f77310a9ddf4db5036066458d7aed92cea9ae Mon Sep 17 00:00:00 2001 +From: Andy Gross <andy.gross@linaro.org> +Date: Sun, 31 Jan 2016 21:28:13 -0600 +Subject: [PATCH] spi: qup: Fix block mode to work correctly + +This patch corrects the behavior of the BLOCK +transactions. During block transactions, the controller +must be read/written to in block size transactions. + +Signed-off-by: Andy Gross <andy.gross@linaro.org> + +Change-Id: I4b4f4d25be57e6e8148f6f0d24bed376eb287ecf +--- + drivers/spi/spi-qup.c | 181 +++++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 141 insertions(+), 40 deletions(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index 089c5e8..e487416 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -83,6 +83,8 @@ + #define QUP_IO_M_MODE_BAM 3 + + /* QUP_OPERATIONAL fields */ ++#define QUP_OP_IN_BLOCK_READ_REQ BIT(13) ++#define QUP_OP_OUT_BLOCK_WRITE_REQ BIT(12) + #define QUP_OP_MAX_INPUT_DONE_FLAG BIT(11) + #define QUP_OP_MAX_OUTPUT_DONE_FLAG BIT(10) + #define QUP_OP_IN_SERVICE_FLAG BIT(9) +@@ -156,6 +158,12 @@ struct spi_qup { + struct dma_slave_config tx_conf; + }; + ++static inline bool spi_qup_is_flag_set(struct spi_qup *controller, u32 flag) ++{ ++ u32 opflag = readl_relaxed(controller->base + QUP_OPERATIONAL); ++ ++ return opflag & flag; ++} + + static inline bool spi_qup_is_dma_xfer(int mode) + { +@@ -217,29 +225,26 @@ static int spi_qup_set_state(struct spi_qup *controller, u32 state) + return 0; + } + +-static void spi_qup_fifo_read(struct spi_qup *controller, +- struct spi_transfer *xfer) ++static void spi_qup_read_from_fifo(struct spi_qup *controller, ++ struct spi_transfer *xfer, u32 num_words) + { + u8 *rx_buf = xfer->rx_buf; +- u32 word, state; +- int idx, shift, w_size; +- +- w_size = controller->w_size; +- +- while (controller->rx_bytes < xfer->len) { ++ int i, shift, num_bytes; ++ u32 word; + +- state = readl_relaxed(controller->base + QUP_OPERATIONAL); +- if (0 == (state & QUP_OP_IN_FIFO_NOT_EMPTY)) +- break; ++ for (; num_words; num_words--) { + + word = readl_relaxed(controller->base + QUP_INPUT_FIFO); + ++ num_bytes = min_t(int, xfer->len - controller->rx_bytes, ++ controller->w_size); ++ + if (!rx_buf) { +- controller->rx_bytes += w_size; ++ controller->rx_bytes += num_bytes; + continue; + } + +- for (idx = 0; idx < w_size; idx++, controller->rx_bytes++) { ++ for (i = 0; i < num_bytes; i++, controller->rx_bytes++) { + /* + * The data format depends on bytes per SPI word: + * 4 bytes: 0x12345678 +@@ -247,38 +252,80 @@ static void spi_qup_fifo_read(struct spi_qup *controller, + * 1 byte : 0x00000012 + */ + shift = BITS_PER_BYTE; +- shift *= (w_size - idx - 1); ++ shift *= (controller->w_size - i - 1); + rx_buf[controller->rx_bytes] = word >> shift; + } + } + } + +-static void spi_qup_fifo_write(struct spi_qup *controller, ++static void spi_qup_read(struct spi_qup *controller, + struct spi_transfer *xfer) + { +- const u8 *tx_buf = xfer->tx_buf; +- u32 word, state, data; +- int idx, w_size; ++ u32 remainder, words_per_block, num_words; ++ bool is_block_mode = controller->mode == QUP_IO_M_MODE_BLOCK; ++ ++ remainder = DIV_ROUND_UP(xfer->len - controller->rx_bytes, ++ controller->w_size); ++ words_per_block = controller->in_blk_sz >> 2; ++ ++ do { ++ /* ACK by clearing service flag */ ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ if (is_block_mode) { ++ num_words = (remainder > words_per_block) ? ++ words_per_block : remainder; ++ } else { ++ if (!spi_qup_is_flag_set(controller, ++ QUP_OP_IN_FIFO_NOT_EMPTY)) ++ break; ++ ++ num_words = 1; ++ } + +- w_size = controller->w_size; ++ /* read up to the maximum transfer size available */ ++ spi_qup_read_from_fifo(controller, xfer, num_words); + +- while (controller->tx_bytes < xfer->len) { ++ remainder -= num_words; + +- state = readl_relaxed(controller->base + QUP_OPERATIONAL); +- if (state & QUP_OP_OUT_FIFO_FULL) ++ /* if block mode, check to see if next block is available */ ++ if (is_block_mode && !spi_qup_is_flag_set(controller, ++ QUP_OP_IN_BLOCK_READ_REQ)) + break; + ++ } while (remainder); ++ ++ /* ++ * Due to extra stickiness of the QUP_OP_IN_SERVICE_FLAG during block ++ * mode reads, it has to be cleared again at the very end ++ */ ++ if (is_block_mode && spi_qup_is_flag_set(controller, ++ QUP_OP_MAX_INPUT_DONE_FLAG)) ++ writel_relaxed(QUP_OP_IN_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++} ++ ++static void spi_qup_write_to_fifo(struct spi_qup *controller, ++ struct spi_transfer *xfer, u32 num_words) ++{ ++ const u8 *tx_buf = xfer->tx_buf; ++ int i, num_bytes; ++ u32 word, data; ++ ++ for (; num_words; num_words--) { + word = 0; +- for (idx = 0; idx < w_size; idx++, controller->tx_bytes++) { + +- if (!tx_buf) { +- controller->tx_bytes += w_size; +- break; ++ num_bytes = min_t(int, xfer->len - controller->tx_bytes, ++ controller->w_size); ++ if (tx_buf) ++ for (i = 0; i < num_bytes; i++) { ++ data = tx_buf[controller->tx_bytes + i]; ++ word |= data << (BITS_PER_BYTE * (3 - i)); + } + +- data = tx_buf[controller->tx_bytes]; +- word |= data << (BITS_PER_BYTE * (3 - idx)); +- } ++ controller->tx_bytes += num_bytes; + + writel_relaxed(word, controller->base + QUP_OUTPUT_FIFO); + } +@@ -291,6 +338,44 @@ static void spi_qup_dma_done(void *data) + complete(done); + } + ++static void spi_qup_write(struct spi_qup *controller, ++ struct spi_transfer *xfer) ++{ ++ bool is_block_mode = controller->mode == QUP_IO_M_MODE_BLOCK; ++ u32 remainder, words_per_block, num_words; ++ ++ remainder = DIV_ROUND_UP(xfer->len - controller->tx_bytes, ++ controller->w_size); ++ words_per_block = controller->out_blk_sz >> 2; ++ ++ do { ++ /* ACK by clearing service flag */ ++ writel_relaxed(QUP_OP_OUT_SERVICE_FLAG, ++ controller->base + QUP_OPERATIONAL); ++ ++ if (is_block_mode) { ++ num_words = (remainder > words_per_block) ? ++ words_per_block : remainder; ++ } else { ++ if (spi_qup_is_flag_set(controller, ++ QUP_OP_OUT_FIFO_FULL)) ++ break; ++ ++ num_words = 1; ++ } ++ ++ spi_qup_write_to_fifo(controller, xfer, num_words); ++ ++ remainder -= num_words; ++ ++ /* if block mode, check to see if next block is available */ ++ if (is_block_mode && !spi_qup_is_flag_set(controller, ++ QUP_OP_OUT_BLOCK_WRITE_REQ)) ++ break; ++ ++ } while (remainder); ++} ++ + static int spi_qup_prep_sg(struct spi_master *master, struct spi_transfer *xfer, + enum dma_transfer_direction dir, + dma_async_tx_callback callback, +@@ -348,11 +433,13 @@ unsigned long timeout) + return ret; + } + +- if (xfer->rx_buf) +- rx_done = spi_qup_dma_done; ++ if (!qup->qup_v1) { ++ if (xfer->rx_buf) ++ rx_done = spi_qup_dma_done; + +- if (xfer->tx_buf) +- tx_done = spi_qup_dma_done; ++ if (xfer->tx_buf) ++ tx_done = spi_qup_dma_done; ++ } + + if (xfer->rx_buf) { + ret = spi_qup_prep_sg(master, xfer, DMA_DEV_TO_MEM, rx_done, +@@ -401,7 +488,7 @@ static int spi_qup_do_pio(struct spi_master *master, struct spi_transfer *xfer, + } + + if (qup->mode == QUP_IO_M_MODE_FIFO) +- spi_qup_fifo_write(qup, xfer); ++ spi_qup_write(qup, xfer); + + ret = spi_qup_set_state(qup, QUP_STATE_RUN); + if (ret) { +@@ -434,10 +521,11 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + + writel_relaxed(qup_err, controller->base + QUP_ERROR_FLAGS); + writel_relaxed(spi_err, controller->base + SPI_ERROR_FLAGS); +- writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); + + if (!xfer) { +- dev_err_ratelimited(controller->dev, "unexpected irq %08x %08x %08x\n", ++ writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); ++ dev_err_ratelimited(controller->dev, ++ "unexpected irq %08x %08x %08x\n", + qup_err, spi_err, opflags); + return IRQ_HANDLED; + } +@@ -463,12 +551,20 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + error = -EIO; + } + +- if (!spi_qup_is_dma_xfer(controller->mode)) { ++ if (spi_qup_is_dma_xfer(controller->mode)) { ++ writel_relaxed(opflags, controller->base + QUP_OPERATIONAL); ++ if (opflags & QUP_OP_IN_SERVICE_FLAG && ++ opflags & QUP_OP_MAX_INPUT_DONE_FLAG) ++ complete(&controller->done); ++ if (opflags & QUP_OP_OUT_SERVICE_FLAG && ++ opflags & QUP_OP_MAX_OUTPUT_DONE_FLAG) ++ complete(&controller->dma_tx_done); ++ } else { + if (opflags & QUP_OP_IN_SERVICE_FLAG) +- spi_qup_fifo_read(controller, xfer); ++ spi_qup_read(controller, xfer); + + if (opflags & QUP_OP_OUT_SERVICE_FLAG) +- spi_qup_fifo_write(controller, xfer); ++ spi_qup_write(controller, xfer); + } + + spin_lock_irqsave(&controller->lock, flags); +@@ -476,6 +572,9 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + controller->xfer = xfer; + spin_unlock_irqrestore(&controller->lock, flags); + ++ /* re-read opflags as flags may have changed due to actions above */ ++ opflags = readl_relaxed(controller->base + QUP_OPERATIONAL); ++ + if ((controller->rx_bytes == xfer->len && + (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error) + complete(&controller->done); +@@ -519,11 +618,13 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) + /* must be zero for FIFO */ + writel_relaxed(0, controller->base + QUP_MX_INPUT_CNT); + writel_relaxed(0, controller->base + QUP_MX_OUTPUT_CNT); +- controller->use_dma = 0; + } else if (spi->master->can_dma && + spi->master->can_dma(spi->master, spi, xfer) && + spi->master->cur_msg_mapped) { + controller->mode = QUP_IO_M_MODE_BAM; ++ writel_relaxed(n_words, controller->base + QUP_MX_INPUT_CNT); ++ writel_relaxed(n_words, controller->base + QUP_MX_OUTPUT_CNT); ++ /* must be zero for BLOCK and BAM */ + writel_relaxed(0, controller->base + QUP_MX_READ_CNT); + writel_relaxed(0, controller->base + QUP_MX_WRITE_CNT); + +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/714-spi-qup-properly-detect-extra-interrupts.patch b/target/linux/ipq806x/patches-4.4/714-spi-qup-properly-detect-extra-interrupts.patch new file mode 100644 index 0000000000..d8a9b31462 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/714-spi-qup-properly-detect-extra-interrupts.patch @@ -0,0 +1,67 @@ +From b69e5e855aaae2dd9f7fc6f4a40af8e6e0cf98ee Mon Sep 17 00:00:00 2001 +From: Matthew McClintock <mmcclint@codeaurora.org> +Date: Thu, 10 Mar 2016 16:44:55 -0600 +Subject: [PATCH] spi: qup: properly detect extra interrupts + +It's possible for a SPI transaction to complete and get another +interrupt and have it processed on the same spi_transfer before the +transfer_one can set it to NULL. + +This masks unexpected interrupts, so let's set the spi_transfer to +NULL in the interrupt once the transaction is done. So we can +properly detect these bad interrupts and print warning messages. + +Change-Id: I0e70ed59fb50e5c48a72a38f74bd178b17c9f24d +Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org> +--- + drivers/spi/spi-qup.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index e487416..45e30c7 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -509,6 +509,7 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + u32 opflags, qup_err, spi_err; + unsigned long flags; + int error = 0; ++ bool done = 0; + + spin_lock_irqsave(&controller->lock, flags); + xfer = controller->xfer; +@@ -567,16 +568,19 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + spi_qup_write(controller, xfer); + } + +- spin_lock_irqsave(&controller->lock, flags); +- controller->error = error; +- controller->xfer = xfer; +- spin_unlock_irqrestore(&controller->lock, flags); +- + /* re-read opflags as flags may have changed due to actions above */ + opflags = readl_relaxed(controller->base + QUP_OPERATIONAL); + + if ((controller->rx_bytes == xfer->len && + (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error) ++ done = true; ++ ++ spin_lock_irqsave(&controller->lock, flags); ++ controller->error = error; ++ controller->xfer = done ? NULL : xfer; ++ spin_unlock_irqrestore(&controller->lock, flags); ++ ++ if (done) + complete(&controller->done); + + return IRQ_HANDLED; +@@ -769,7 +773,6 @@ static int spi_qup_transfer_one(struct spi_master *master, + exit: + spi_qup_set_state(controller, QUP_STATE_RESET); + spin_lock_irqsave(&controller->lock, flags); +- controller->xfer = NULL; + if (!ret) + ret = controller->error; + spin_unlock_irqrestore(&controller->lock, flags); +-- +2.7.2 + diff --git a/target/linux/ipq806x/patches-4.4/715-spi-qup-don-t-re-read-opflags-to-see-if-transaction-.patch b/target/linux/ipq806x/patches-4.4/715-spi-qup-don-t-re-read-opflags-to-see-if-transaction-.patch new file mode 100644 index 0000000000..54711a1d05 --- /dev/null +++ b/target/linux/ipq806x/patches-4.4/715-spi-qup-don-t-re-read-opflags-to-see-if-transaction-.patch @@ -0,0 +1,32 @@ +From f57ff801665b868d8607c9e872466b54982564bc Mon Sep 17 00:00:00 2001 +From: Matthew McClintock <mmcclint@codeaurora.org> +Date: Thu, 10 Mar 2016 16:48:27 -0600 +Subject: [PATCH] spi: qup: don't re-read opflags to see if transaction is done + for reads + +For reads, we will get another interrupt so we need to handle things +then. For writes, we can finish up now. + +Change-Id: I4fa95ae7bb9d78f8ba54c613b981b37d4ea81d7e +Signed-off-by: Matthew McClintock <mmcclint@codeaurora.org> +--- + drivers/spi/spi-qup.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c +index 45e30c7..59bc37c 100644 +--- a/drivers/spi/spi-qup.c ++++ b/drivers/spi/spi-qup.c +@@ -569,7 +569,8 @@ static irqreturn_t spi_qup_qup_irq(int irq, void *dev_id) + } + + /* re-read opflags as flags may have changed due to actions above */ +- opflags = readl_relaxed(controller->base + QUP_OPERATIONAL); ++ if (opflags & QUP_OP_OUT_SERVICE_FLAG) ++ opflags = readl_relaxed(controller->base + QUP_OPERATIONAL); + + if ((controller->rx_bytes == xfer->len && + (opflags & QUP_OP_MAX_INPUT_DONE_FLAG)) || error) +-- +2.7.2 + |