summaryrefslogtreecommitdiff
path: root/target/linux/ipq806x/patches-4.4
diff options
context:
space:
mode:
authorJohn Crispin <john@phrozen.org>2016-08-11 16:34:08 +0200
committerJohn Crispin <john@phrozen.org>2016-08-18 09:49:18 +0200
commitd7e4b9babb6ce8bf66c4c2e721b78c30d09afdda (patch)
tree7808c469b98212bd9da8b8aa7132829946664a3c /target/linux/ipq806x/patches-4.4
parent5e563262f12d919f5062b5e47d60d0be64d33d35 (diff)
downloadmtk-20170518-d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda.zip
mtk-20170518-d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda.tar.gz
mtk-20170518-d7e4b9babb6ce8bf66c4c2e721b78c30d09afdda.tar.bz2
ipq806x: sync with latest patches sent by QCA
Signed-off-by: John Crispin <john@phrozen.org>
Diffstat (limited to 'target/linux/ipq806x/patches-4.4')
-rw-r--r--target/linux/ipq806x/patches-4.4/097-usb-dwc3-add-generic-OF-glue-layer.patch244
-rw-r--r--target/linux/ipq806x/patches-4.4/098-usb-dwc3-of-simple-fix-build-warning-on-PM.patch41
-rw-r--r--target/linux/ipq806x/patches-4.4/099-usb-dwc3-Remove-impossible-check-for-of_clk_get_pare.patch52
-rw-r--r--target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch186
-rw-r--r--target/linux/ipq806x/patches-4.4/710-spi-qup-Make-sure-mode-is-only-determined-once.patch225
-rw-r--r--target/linux/ipq806x/patches-4.4/711-spi-qup-Fix-transaction-done-signaling.patch35
-rw-r--r--target/linux/ipq806x/patches-4.4/712-spi-qup-Fix-DMA-mode-to-work-correctly.patch226
-rw-r--r--target/linux/ipq806x/patches-4.4/713-spi-qup-Fix-block-mode-to-work-correctly.patch317
-rw-r--r--target/linux/ipq806x/patches-4.4/714-spi-qup-properly-detect-extra-interrupts.patch67
-rw-r--r--target/linux/ipq806x/patches-4.4/715-spi-qup-don-t-re-read-opflags-to-see-if-transaction-.patch32
10 files changed, 1333 insertions, 92 deletions
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 0000000..96e9859
--- /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 0000000..2fbdd1b
--- /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 0000000..44506c1
--- /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 4bba0ac..d554919 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 0000000..e5dafd7
--- /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 0000000..dd4bad3
--- /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 0000000..d9abc65
--- /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 0000000..7a05ecd
--- /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 0000000..d8a9b31
--- /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 0000000..54711a1
--- /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
+