ipq806x: sync with latest patches sent by QCA
authorJohn Crispin <john@phrozen.org>
Thu, 11 Aug 2016 14:34:08 +0000 (16:34 +0200)
committerJohn Crispin <john@phrozen.org>
Thu, 18 Aug 2016 07:49:18 +0000 (09:49 +0200)
Signed-off-by: John Crispin <john@phrozen.org>
target/linux/ipq806x/modules.mk
target/linux/ipq806x/patches-4.4/097-usb-dwc3-add-generic-OF-glue-layer.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/098-usb-dwc3-of-simple-fix-build-warning-on-PM.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/099-usb-dwc3-Remove-impossible-check-for-of_clk_get_pare.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/100-usb-phy-Add-Qualcomm-DWC3-HS-SS-PHY-drivers.patch
target/linux/ipq806x/patches-4.4/710-spi-qup-Make-sure-mode-is-only-determined-once.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/711-spi-qup-Fix-transaction-done-signaling.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/712-spi-qup-Fix-DMA-mode-to-work-correctly.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/713-spi-qup-Fix-block-mode-to-work-correctly.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/714-spi-qup-properly-detect-extra-interrupts.patch [new file with mode: 0644]
target/linux/ipq806x/patches-4.4/715-spi-qup-don-t-re-read-opflags-to-see-if-transaction-.patch [new file with mode: 0644]

index 04a2d4f..38dd195 100644 (file)
@@ -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 (file)
index 0000000..96e9859
--- /dev/null
@@ -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 (file)
index 0000000..2fbdd1b
--- /dev/null
@@ -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 (file)
index 0000000..44506c1
--- /dev/null
@@ -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
+
index 4bba0ac..d554919 100644 (file)
@@ -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
  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)
 +#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
 + *
 + * @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;
 +}
 +
 +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)
 +       * 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);
 +
 +      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;
 +
 +      /*
 +      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)
 +      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
 +      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;
 +      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;
 +      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);
 +      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);
 +{
 +      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;
 +
 +              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)) {
 +              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 (file)
index 0000000..e5dafd7
--- /dev/null
@@ -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 (file)
index 0000000..dd4bad3
--- /dev/null
@@ -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 (file)
index 0000000..d9abc65
--- /dev/null
@@ -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 (file)
index 0000000..7a05ecd
--- /dev/null
@@ -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 (file)
index 0000000..d8a9b31
--- /dev/null
@@ -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 (file)
index 0000000..54711a1
--- /dev/null
@@ -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
+