Merge remote-tracking branches 'spi/topic/lp8841', 'spi/topic/msg', 'spi/topic/pl022...
authorMark Brown <broonie@kernel.org>
Fri, 11 Mar 2016 07:28:43 +0000 (14:28 +0700)
committerMark Brown <broonie@kernel.org>
Fri, 11 Mar 2016 07:28:43 +0000 (14:28 +0700)
12 files changed:
Documentation/devicetree/bindings/spi/icpdas-lp8841-spi-rtc.txt [new file with mode: 0644]
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/spi-lp8841-rtc.c [new file with mode: 0644]
drivers/spi/spi-pl022.c
drivers/spi/spi-pxa2xx-dma.c
drivers/spi/spi-pxa2xx-pci.c
drivers/spi/spi-pxa2xx.c
drivers/spi/spi-pxa2xx.h
drivers/spi/spi.c
include/linux/pxa2xx_ssp.h
include/linux/spi/spi.h

diff --git a/Documentation/devicetree/bindings/spi/icpdas-lp8841-spi-rtc.txt b/Documentation/devicetree/bindings/spi/icpdas-lp8841-spi-rtc.txt
new file mode 100644 (file)
index 0000000..852b651
--- /dev/null
@@ -0,0 +1,54 @@
+* ICP DAS LP-8841 SPI Controller for RTC
+
+ICP DAS LP-8841 contains a DS-1302 RTC. RTC is connected to an IO
+memory register, which acts as an SPI master device.
+
+The device uses the standard MicroWire half-duplex transfer timing.
+Master output is set on low clock and sensed by the RTC on the rising
+edge. Master input is set by the RTC on the trailing edge and is sensed
+by the master on low clock.
+
+Required properties:
+
+- #address-cells: should be 1
+
+- #size-cells: should be 0
+
+- compatible: should be "icpdas,lp8841-spi-rtc"
+
+- reg: should provide IO memory address
+
+Requirements to SPI slave nodes:
+
+- There can be only one slave device.
+
+- The spi slave node should claim the following flags which are
+  required by the spi controller.
+
+  - spi-3wire: The master itself has only 3 wire. It cannor work in
+    full duplex mode.
+
+  - spi-cs-high: DS-1302 has active high chip select line. The master
+    doesn't support active low.
+
+  - spi-lsb-first: DS-1302 requires least significant bit first
+    transfers. The master only support this type of bit ordering.
+
+
+Example:
+
+spi@901c {
+       #address-cells = <1>;
+       #size-cells = <0>;
+       compatible = "icpdas,lp8841-spi-rtc";
+       reg = <0x901c 0x1>;
+
+       rtc@0 {
+               compatible = "maxim,ds1302";
+               reg = <0>;
+               spi-max-frequency = <500000>;
+               spi-3wire;
+               spi-lsb-first;
+               spi-cs-high;
+       };
+};
index 77d3202..c2a0404 100644 (file)
@@ -294,6 +294,16 @@ config SPI_LM70_LLP
          which interfaces to an LM70 temperature sensor using
          a parallel port.
 
+config SPI_LP8841_RTC
+       tristate "ICP DAS LP-8841 SPI Controller for RTC"
+       depends on MACH_PXA27X_DT || COMPILE_TEST
+       help
+         This driver provides an SPI master device to drive Maxim
+         DS-1302 real time clock.
+
+         Say N here unless you plan to run the kernel on an ICP DAS
+         LP-8x4x industrial computer.
+
 config SPI_MPC52xx
        tristate "Freescale MPC52xx SPI (non-PSC) controller support"
        depends on PPC_MPC52xx
@@ -445,10 +455,6 @@ config SPI_PPC4xx
        help
          This selects a driver for the PPC4xx SPI Controller.
 
-config SPI_PXA2XX_DMA
-       def_bool y
-       depends on SPI_PXA2XX
-
 config SPI_PXA2XX
        tristate "PXA2xx SSP SPI master"
        depends on (ARCH_PXA || PCI || ACPI)
index 7ad8806..fbb255c 100644 (file)
@@ -47,6 +47,7 @@ obj-$(CONFIG_SPI_GPIO)                        += spi-gpio.o
 obj-$(CONFIG_SPI_IMG_SPFI)             += spi-img-spfi.o
 obj-$(CONFIG_SPI_IMX)                  += spi-imx.o
 obj-$(CONFIG_SPI_LM70_LLP)             += spi-lm70llp.o
+obj-$(CONFIG_SPI_LP8841_RTC)           += spi-lp8841-rtc.o
 obj-$(CONFIG_SPI_MESON_SPIFC)          += spi-meson-spifc.o
 obj-$(CONFIG_SPI_MPC512x_PSC)          += spi-mpc512x-psc.o
 obj-$(CONFIG_SPI_MPC52xx_PSC)          += spi-mpc52xx-psc.o
@@ -63,8 +64,7 @@ obj-$(CONFIG_SPI_TI_QSPI)             += spi-ti-qspi.o
 obj-$(CONFIG_SPI_ORION)                        += spi-orion.o
 obj-$(CONFIG_SPI_PL022)                        += spi-pl022.o
 obj-$(CONFIG_SPI_PPC4xx)               += spi-ppc4xx.o
-spi-pxa2xx-platform-objs               := spi-pxa2xx.o
-spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA)   += spi-pxa2xx-dma.o
+spi-pxa2xx-platform-objs               := spi-pxa2xx.o spi-pxa2xx-dma.o
 obj-$(CONFIG_SPI_PXA2XX)               += spi-pxa2xx-platform.o
 obj-$(CONFIG_SPI_PXA2XX_PCI)           += spi-pxa2xx-pci.o
 obj-$(CONFIG_SPI_QUP)                  += spi-qup.o
diff --git a/drivers/spi/spi-lp8841-rtc.c b/drivers/spi/spi-lp8841-rtc.c
new file mode 100644 (file)
index 0000000..faa577d
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * SPI master driver for ICP DAS LP-8841 RTC
+ *
+ * Copyright (C) 2016 Sergei Ianovich
+ *
+ * based on
+ *
+ * Dallas DS1302 RTC Support
+ * Copyright (C) 2002 David McCullough
+ * Copyright (C) 2003 - 2007 Paul Mundt
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+
+#define DRIVER_NAME    "spi_lp8841_rtc"
+
+#define SPI_LP8841_RTC_CE      0x01
+#define SPI_LP8841_RTC_CLK     0x02
+#define SPI_LP8841_RTC_nWE     0x04
+#define SPI_LP8841_RTC_MOSI    0x08
+#define SPI_LP8841_RTC_MISO    0x01
+
+/*
+ * REVISIT If there is support for SPI_3WIRE and SPI_LSB_FIRST in SPI
+ * GPIO driver, this SPI driver can be replaced by a simple GPIO driver
+ * providing 3 GPIO pins.
+ */
+
+struct spi_lp8841_rtc {
+       void            *iomem;
+       unsigned long   state;
+};
+
+static inline void
+setsck(struct spi_lp8841_rtc *data, int is_on)
+{
+       if (is_on)
+               data->state |= SPI_LP8841_RTC_CLK;
+       else
+               data->state &= ~SPI_LP8841_RTC_CLK;
+       writeb(data->state, data->iomem);
+}
+
+static inline void
+setmosi(struct spi_lp8841_rtc *data, int is_on)
+{
+       if (is_on)
+               data->state |= SPI_LP8841_RTC_MOSI;
+       else
+               data->state &= ~SPI_LP8841_RTC_MOSI;
+       writeb(data->state, data->iomem);
+}
+
+static inline int
+getmiso(struct spi_lp8841_rtc *data)
+{
+       return ioread8(data->iomem) & SPI_LP8841_RTC_MISO;
+}
+
+static inline u32
+bitbang_txrx_be_cpha0_lsb(struct spi_lp8841_rtc *data,
+               unsigned usecs, unsigned cpol, unsigned flags,
+               u32 word, u8 bits)
+{
+       /* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */
+
+       u32 shift = 32 - bits;
+       /* clock starts at inactive polarity */
+       for (; likely(bits); bits--) {
+
+               /* setup LSB (to slave) on leading edge */
+               if ((flags & SPI_MASTER_NO_TX) == 0)
+                       setmosi(data, (word & 1));
+
+               usleep_range(usecs, usecs + 1); /* T(setup) */
+
+               /* sample LSB (from slave) on trailing edge */
+               word >>= 1;
+               if ((flags & SPI_MASTER_NO_RX) == 0)
+                       word |= (getmiso(data) << 31);
+
+               setsck(data, !cpol);
+               usleep_range(usecs, usecs + 1);
+
+               setsck(data, cpol);
+       }
+
+       word >>= shift;
+       return word;
+}
+
+static int
+spi_lp8841_rtc_transfer_one(struct spi_master *master,
+                           struct spi_device *spi,
+                           struct spi_transfer *t)
+{
+       struct spi_lp8841_rtc   *data = spi_master_get_devdata(master);
+       unsigned                count = t->len;
+       const u8                *tx = t->tx_buf;
+       u8                      *rx = t->rx_buf;
+       u8                      word = 0;
+       int                     ret = 0;
+
+       if (tx) {
+               data->state &= ~SPI_LP8841_RTC_nWE;
+               writeb(data->state, data->iomem);
+               while (likely(count > 0)) {
+                       word = *tx++;
+                       bitbang_txrx_be_cpha0_lsb(data, 1, 0,
+                                       SPI_MASTER_NO_RX, word, 8);
+                       count--;
+               }
+       } else if (rx) {
+               data->state |= SPI_LP8841_RTC_nWE;
+               writeb(data->state, data->iomem);
+               while (likely(count > 0)) {
+                       word = bitbang_txrx_be_cpha0_lsb(data, 1, 0,
+                                       SPI_MASTER_NO_TX, word, 8);
+                       *rx++ = word;
+                       count--;
+               }
+       } else {
+               ret = -EINVAL;
+       }
+
+       spi_finalize_current_transfer(master);
+
+       return ret;
+}
+
+static void
+spi_lp8841_rtc_set_cs(struct spi_device *spi, bool enable)
+{
+       struct spi_lp8841_rtc *data = spi_master_get_devdata(spi->master);
+
+       data->state = 0;
+       writeb(data->state, data->iomem);
+       if (enable) {
+               usleep_range(4, 5);
+               data->state |= SPI_LP8841_RTC_CE;
+               writeb(data->state, data->iomem);
+               usleep_range(4, 5);
+       }
+}
+
+static int
+spi_lp8841_rtc_setup(struct spi_device *spi)
+{
+       if ((spi->mode & SPI_CS_HIGH) == 0) {
+               dev_err(&spi->dev, "unsupported active low chip select\n");
+               return -EINVAL;
+       }
+
+       if ((spi->mode & SPI_LSB_FIRST) == 0) {
+               dev_err(&spi->dev, "unsupported MSB first mode\n");
+               return -EINVAL;
+       }
+
+       if ((spi->mode & SPI_3WIRE) == 0) {
+               dev_err(&spi->dev, "unsupported wiring. 3 wires required\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id spi_lp8841_rtc_dt_ids[] = {
+       { .compatible = "icpdas,lp8841-spi-rtc" },
+       { }
+};
+
+MODULE_DEVICE_TABLE(of, spi_lp8841_rtc_dt_ids);
+#endif
+
+static int
+spi_lp8841_rtc_probe(struct platform_device *pdev)
+{
+       int                             ret;
+       struct spi_master               *master;
+       struct spi_lp8841_rtc           *data;
+       void                            *iomem;
+
+       master = spi_alloc_master(&pdev->dev, sizeof(*data));
+       if (!master)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, master);
+
+       master->flags = SPI_MASTER_HALF_DUPLEX;
+       master->mode_bits = SPI_CS_HIGH | SPI_3WIRE | SPI_LSB_FIRST;
+
+       master->bus_num = pdev->id;
+       master->num_chipselect = 1;
+       master->setup = spi_lp8841_rtc_setup;
+       master->set_cs = spi_lp8841_rtc_set_cs;
+       master->transfer_one = spi_lp8841_rtc_transfer_one;
+       master->bits_per_word_mask = SPI_BPW_MASK(8);
+#ifdef CONFIG_OF
+       master->dev.of_node = pdev->dev.of_node;
+#endif
+
+       data = spi_master_get_devdata(master);
+
+       iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       data->iomem = devm_ioremap_resource(&pdev->dev, iomem);
+       ret = PTR_ERR_OR_ZERO(data->iomem);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get IO address\n");
+               goto err_put_master;
+       }
+
+       /* register with the SPI framework */
+       ret = devm_spi_register_master(&pdev->dev, master);
+       if (ret) {
+               dev_err(&pdev->dev, "cannot register spi master\n");
+               goto err_put_master;
+       }
+
+       return ret;
+
+
+err_put_master:
+       spi_master_put(master);
+
+       return ret;
+}
+
+MODULE_ALIAS("platform:" DRIVER_NAME);
+
+static struct platform_driver spi_lp8841_rtc_driver = {
+       .driver = {
+               .name   = DRIVER_NAME,
+               .of_match_table = of_match_ptr(spi_lp8841_rtc_dt_ids),
+       },
+       .probe          = spi_lp8841_rtc_probe,
+};
+module_platform_driver(spi_lp8841_rtc_driver);
+
+MODULE_DESCRIPTION("SPI master driver for ICP DAS LP-8841 RTC");
+MODULE_AUTHOR("Sergei Ianovich");
+MODULE_LICENSE("GPL");
index 5e5fd77..f7f7ba1 100644 (file)
@@ -346,13 +346,6 @@ struct vendor_data {
  * @clk: outgoing clock "SPICLK" for the SPI bus
  * @master: SPI framework hookup
  * @master_info: controller-specific data from machine setup
- * @kworker: thread struct for message pump
- * @kworker_task: pointer to task for message pump kworker thread
- * @pump_messages: work struct for scheduling work to the message pump
- * @queue_lock: spinlock to syncronise access to message queue
- * @queue: message queue
- * @busy: message pump is busy
- * @running: message pump is running
  * @pump_transfers: Tasklet used in Interrupt Transfer mode
  * @cur_msg: Pointer to current spi_message being processed
  * @cur_transfer: Pointer to current spi_transfer
index bd8b369..365fc22 100644 (file)
@@ -254,8 +254,8 @@ irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
        if (status & SSSR_ROR) {
                dev_err(&drv_data->pdev->dev, "FIFO overrun\n");
 
-               dmaengine_terminate_all(drv_data->rx_chan);
-               dmaengine_terminate_all(drv_data->tx_chan);
+               dmaengine_terminate_async(drv_data->rx_chan);
+               dmaengine_terminate_async(drv_data->tx_chan);
 
                pxa2xx_spi_dma_transfer_complete(drv_data, true);
                return IRQ_HANDLED;
@@ -331,13 +331,13 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
 void pxa2xx_spi_dma_release(struct driver_data *drv_data)
 {
        if (drv_data->rx_chan) {
-               dmaengine_terminate_all(drv_data->rx_chan);
+               dmaengine_terminate_sync(drv_data->rx_chan);
                dma_release_channel(drv_data->rx_chan);
                sg_free_table(&drv_data->rx_sgt);
                drv_data->rx_chan = NULL;
        }
        if (drv_data->tx_chan) {
-               dmaengine_terminate_all(drv_data->tx_chan);
+               dmaengine_terminate_sync(drv_data->tx_chan);
                dma_release_channel(drv_data->tx_chan);
                sg_free_table(&drv_data->tx_sgt);
                drv_data->tx_chan = NULL;
index d19d7f2..520ed1d 100644 (file)
@@ -19,6 +19,7 @@ enum {
        PORT_BSW1,
        PORT_BSW2,
        PORT_QUARK_X1000,
+       PORT_LPT,
 };
 
 struct pxa_spi_info {
@@ -42,6 +43,9 @@ static struct dw_dma_slave bsw1_rx_param = { .src_id = 7 };
 static struct dw_dma_slave bsw2_tx_param = { .dst_id = 8 };
 static struct dw_dma_slave bsw2_rx_param = { .src_id = 9 };
 
+static struct dw_dma_slave lpt_tx_param = { .dst_id = 0 };
+static struct dw_dma_slave lpt_rx_param = { .src_id = 1 };
+
 static bool lpss_dma_filter(struct dma_chan *chan, void *param)
 {
        struct dw_dma_slave *dws = param;
@@ -98,6 +102,14 @@ static struct pxa_spi_info spi_info_configs[] = {
                .num_chipselect = 1,
                .max_clk_rate = 50000000,
        },
+       [PORT_LPT] = {
+               .type = LPSS_LPT_SSP,
+               .port_id = 0,
+               .num_chipselect = 1,
+               .max_clk_rate = 50000000,
+               .tx_param = &lpt_tx_param,
+               .rx_param = &lpt_rx_param,
+       },
 };
 
 static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
@@ -202,6 +214,7 @@ static const struct pci_device_id pxa2xx_spi_pci_devices[] = {
        { PCI_VDEVICE(INTEL, 0x228e), PORT_BSW0 },
        { PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 },
        { PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 },
+       { PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT },
        { },
 };
 MODULE_DEVICE_TABLE(pci, pxa2xx_spi_pci_devices);
index ab9914a..85e59a4 100644 (file)
@@ -65,8 +65,6 @@ MODULE_ALIAS("platform:pxa2xx-spi");
 #define LPSS_GENERAL_REG_RXTO_HOLDOFF_DISABLE  BIT(24)
 #define LPSS_CS_CONTROL_SW_MODE                        BIT(0)
 #define LPSS_CS_CONTROL_CS_HIGH                        BIT(1)
-#define LPSS_CS_CONTROL_CS_SEL_SHIFT           8
-#define LPSS_CS_CONTROL_CS_SEL_MASK            (3 << LPSS_CS_CONTROL_CS_SEL_SHIFT)
 #define LPSS_CAPS_CS_EN_SHIFT                  9
 #define LPSS_CAPS_CS_EN_MASK                   (0xf << LPSS_CAPS_CS_EN_SHIFT)
 
@@ -82,6 +80,10 @@ struct lpss_config {
        u32 rx_threshold;
        u32 tx_threshold_lo;
        u32 tx_threshold_hi;
+       /* Chip select control */
+       unsigned cs_sel_shift;
+       unsigned cs_sel_mask;
+       unsigned cs_num;
 };
 
 /* Keep these sorted with enum pxa_ssp_type */
@@ -106,6 +108,19 @@ static const struct lpss_config lpss_platforms[] = {
                .tx_threshold_lo = 160,
                .tx_threshold_hi = 224,
        },
+       {       /* LPSS_BSW_SSP */
+               .offset = 0x400,
+               .reg_general = 0x08,
+               .reg_ssp = 0x0c,
+               .reg_cs_ctrl = 0x18,
+               .reg_capabilities = -1,
+               .rx_threshold = 64,
+               .tx_threshold_lo = 160,
+               .tx_threshold_hi = 224,
+               .cs_sel_shift = 2,
+               .cs_sel_mask = 1 << 2,
+               .cs_num = 2,
+       },
        {       /* LPSS_SPT_SSP */
                .offset = 0x200,
                .reg_general = -1,
@@ -125,6 +140,8 @@ static const struct lpss_config lpss_platforms[] = {
                .rx_threshold = 1,
                .tx_threshold_lo = 16,
                .tx_threshold_hi = 48,
+               .cs_sel_shift = 8,
+               .cs_sel_mask = 3 << 8,
        },
 };
 
@@ -139,6 +156,7 @@ static bool is_lpss_ssp(const struct driver_data *drv_data)
        switch (drv_data->ssp_type) {
        case LPSS_LPT_SSP:
        case LPSS_BYT_SSP:
+       case LPSS_BSW_SSP:
        case LPSS_SPT_SSP:
        case LPSS_BXT_SSP:
                return true;
@@ -288,37 +306,50 @@ static void lpss_ssp_setup(struct driver_data *drv_data)
        }
 }
 
+static void lpss_ssp_select_cs(struct driver_data *drv_data,
+                              const struct lpss_config *config)
+{
+       u32 value, cs;
+
+       if (!config->cs_sel_mask)
+               return;
+
+       value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl);
+
+       cs = drv_data->cur_msg->spi->chip_select;
+       cs <<= config->cs_sel_shift;
+       if (cs != (value & config->cs_sel_mask)) {
+               /*
+                * When switching another chip select output active the
+                * output must be selected first and wait 2 ssp_clk cycles
+                * before changing state to active. Otherwise a short
+                * glitch will occur on the previous chip select since
+                * output select is latched but state control is not.
+                */
+               value &= ~config->cs_sel_mask;
+               value |= cs;
+               __lpss_ssp_write_priv(drv_data,
+                                     config->reg_cs_ctrl, value);
+               ndelay(1000000000 /
+                      (drv_data->master->max_speed_hz / 2));
+       }
+}
+
 static void lpss_ssp_cs_control(struct driver_data *drv_data, bool enable)
 {
        const struct lpss_config *config;
-       u32 value, cs;
+       u32 value;
 
        config = lpss_get_config(drv_data);
 
+       if (enable)
+               lpss_ssp_select_cs(drv_data, config);
+
        value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl);
-       if (enable) {
-               cs = drv_data->cur_msg->spi->chip_select;
-               cs <<= LPSS_CS_CONTROL_CS_SEL_SHIFT;
-               if (cs != (value & LPSS_CS_CONTROL_CS_SEL_MASK)) {
-                       /*
-                        * When switching another chip select output active
-                        * the output must be selected first and wait 2 ssp_clk
-                        * cycles before changing state to active. Otherwise
-                        * a short glitch will occur on the previous chip
-                        * select since output select is latched but state
-                        * control is not.
-                        */
-                       value &= ~LPSS_CS_CONTROL_CS_SEL_MASK;
-                       value |= cs;
-                       __lpss_ssp_write_priv(drv_data,
-                                             config->reg_cs_ctrl, value);
-                       ndelay(1000000000 /
-                              (drv_data->master->max_speed_hz / 2));
-               }
+       if (enable)
                value &= ~LPSS_CS_CONTROL_CS_HIGH;
-       } else {
+       else
                value |= LPSS_CS_CONTROL_CS_HIGH;
-       }
        __lpss_ssp_write_priv(drv_data, config->reg_cs_ctrl, value);
 }
 
@@ -496,6 +527,7 @@ static void giveback(struct driver_data *drv_data)
 {
        struct spi_transfer* last_transfer;
        struct spi_message *msg;
+       unsigned long timeout;
 
        msg = drv_data->cur_msg;
        drv_data->cur_msg = NULL;
@@ -508,6 +540,12 @@ static void giveback(struct driver_data *drv_data)
        if (last_transfer->delay_usecs)
                udelay(last_transfer->delay_usecs);
 
+       /* Wait until SSP becomes idle before deasserting the CS */
+       timeout = jiffies + msecs_to_jiffies(10);
+       while (pxa2xx_spi_read(drv_data, SSSR) & SSSR_BSY &&
+              !time_after(jiffies, timeout))
+               cpu_relax();
+
        /* Drop chip select UNLESS cs_change is true or we are returning
         * a message with an error, or next message is for another chip
         */
@@ -572,7 +610,7 @@ static void int_error_stop(struct driver_data *drv_data, const char* msg)
 
 static void int_transfer_complete(struct driver_data *drv_data)
 {
-       /* Stop SSP */
+       /* Clear and disable interrupts */
        write_SSSR_CS(drv_data, drv_data->clear_sr);
        reset_sccr1(drv_data);
        if (!pxa25x_ssp_comp(drv_data))
@@ -957,8 +995,6 @@ static void pump_transfers(unsigned long data)
        drv_data->tx_end = drv_data->tx + transfer->len;
        drv_data->rx = transfer->rx_buf;
        drv_data->rx_end = drv_data->rx + transfer->len;
-       drv_data->rx_dma = transfer->rx_dma;
-       drv_data->tx_dma = transfer->tx_dma;
        drv_data->len = transfer->len;
        drv_data->write = drv_data->tx ? chip->write : null_writer;
        drv_data->read = drv_data->rx ? chip->read : null_reader;
@@ -1001,19 +1037,6 @@ static void pump_transfers(unsigned long data)
                                             "pump_transfers: DMA burst size reduced to match bits_per_word\n");
        }
 
-       /* NOTE:  PXA25x_SSP _could_ use external clocking ... */
-       cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
-       if (!pxa25x_ssp_comp(drv_data))
-               dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
-                       drv_data->master->max_speed_hz
-                               / (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
-                       chip->enable_dma ? "DMA" : "PIO");
-       else
-               dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
-                       drv_data->master->max_speed_hz / 2
-                               / (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
-                       chip->enable_dma ? "DMA" : "PIO");
-
        message->state = RUNNING_STATE;
 
        drv_data->dma_mapped = 0;
@@ -1040,6 +1063,19 @@ static void pump_transfers(unsigned long data)
                write_SSSR_CS(drv_data, drv_data->clear_sr);
        }
 
+       /* NOTE:  PXA25x_SSP _could_ use external clocking ... */
+       cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
+       if (!pxa25x_ssp_comp(drv_data))
+               dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
+                       drv_data->master->max_speed_hz
+                               / (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
+                       drv_data->dma_mapped ? "DMA" : "PIO");
+       else
+               dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
+                       drv_data->master->max_speed_hz / 2
+                               / (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
+                       drv_data->dma_mapped ? "DMA" : "PIO");
+
        if (is_lpss_ssp(drv_data)) {
                if ((pxa2xx_spi_read(drv_data, SSIRF) & 0xff)
                    != chip->lpss_rx_threshold)
@@ -1166,6 +1202,7 @@ static int setup(struct spi_device *spi)
                break;
        case LPSS_LPT_SSP:
        case LPSS_BYT_SSP:
+       case LPSS_BSW_SSP:
        case LPSS_SPT_SSP:
        case LPSS_BXT_SSP:
                config = lpss_get_config(drv_data);
@@ -1313,7 +1350,7 @@ static const struct acpi_device_id pxa2xx_spi_acpi_match[] = {
        { "INT3430", LPSS_LPT_SSP },
        { "INT3431", LPSS_LPT_SSP },
        { "80860F0E", LPSS_BYT_SSP },
-       { "8086228E", LPSS_BYT_SSP },
+       { "8086228E", LPSS_BSW_SSP },
        { },
 };
 MODULE_DEVICE_TABLE(acpi, pxa2xx_spi_acpi_match);
@@ -1347,10 +1384,14 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = {
        /* SPT-H */
        { PCI_VDEVICE(INTEL, 0xa129), LPSS_SPT_SSP },
        { PCI_VDEVICE(INTEL, 0xa12a), LPSS_SPT_SSP },
-       /* BXT */
+       /* BXT A-Step */
        { PCI_VDEVICE(INTEL, 0x0ac2), LPSS_BXT_SSP },
        { PCI_VDEVICE(INTEL, 0x0ac4), LPSS_BXT_SSP },
        { PCI_VDEVICE(INTEL, 0x0ac6), LPSS_BXT_SSP },
+       /* BXT B-Step */
+       { PCI_VDEVICE(INTEL, 0x1ac2), LPSS_BXT_SSP },
+       { PCI_VDEVICE(INTEL, 0x1ac4), LPSS_BXT_SSP },
+       { PCI_VDEVICE(INTEL, 0x1ac6), LPSS_BXT_SSP },
        /* APL */
        { PCI_VDEVICE(INTEL, 0x5ac2), LPSS_BXT_SSP },
        { PCI_VDEVICE(INTEL, 0x5ac4), LPSS_BXT_SSP },
@@ -1438,6 +1479,29 @@ pxa2xx_spi_init_pdata(struct platform_device *pdev)
 }
 #endif
 
+static int pxa2xx_spi_fw_translate_cs(struct spi_master *master, unsigned cs)
+{
+       struct driver_data *drv_data = spi_master_get_devdata(master);
+
+       if (has_acpi_companion(&drv_data->pdev->dev)) {
+               switch (drv_data->ssp_type) {
+               /*
+                * For Atoms the ACPI DeviceSelection used by the Windows
+                * driver starts from 1 instead of 0 so translate it here
+                * to match what Linux expects.
+                */
+               case LPSS_BYT_SSP:
+               case LPSS_BSW_SSP:
+                       return cs - 1;
+
+               default:
+                       break;
+               }
+       }
+
+       return cs;
+}
+
 static int pxa2xx_spi_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -1490,6 +1554,7 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
        master->setup = setup;
        master->transfer_one_message = pxa2xx_spi_transfer_one_message;
        master->unprepare_transfer_hardware = pxa2xx_spi_unprepare_transfer;
+       master->fw_translate_cs = pxa2xx_spi_fw_translate_cs;
        master->auto_runtime_pm = true;
 
        drv_data->ssp_type = ssp->type;
@@ -1576,6 +1641,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
                        tmp &= LPSS_CAPS_CS_EN_MASK;
                        tmp >>= LPSS_CAPS_CS_EN_SHIFT;
                        platform_info->num_chipselect = ffz(tmp);
+               } else if (config->cs_num) {
+                       platform_info->num_chipselect = config->cs_num;
                }
        }
        master->num_chipselect = platform_info->num_chipselect;
index 58efa98..a1ef889 100644 (file)
@@ -69,8 +69,6 @@ struct driver_data {
        void *rx;
        void *rx_end;
        int dma_mapped;
-       dma_addr_t rx_dma;
-       dma_addr_t tx_dma;
        size_t rx_map_len;
        size_t tx_map_len;
        u8 n_bytes;
@@ -147,20 +145,9 @@ static inline void write_SSSR_CS(struct driver_data *drv_data, u32 val)
 extern int pxa2xx_spi_flush(struct driver_data *drv_data);
 extern void *pxa2xx_spi_next_transfer(struct driver_data *drv_data);
 
-/*
- * Select the right DMA implementation.
- */
-#if defined(CONFIG_SPI_PXA2XX_DMA)
-#define SPI_PXA2XX_USE_DMA     1
 #define MAX_DMA_LEN            SZ_64K
 #define DEFAULT_DMA_CR1                (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL)
-#else
-#undef SPI_PXA2XX_USE_DMA
-#define MAX_DMA_LEN            0
-#define DEFAULT_DMA_CR1                0
-#endif
 
-#ifdef SPI_PXA2XX_USE_DMA
 extern bool pxa2xx_spi_dma_is_possible(size_t len);
 extern int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data);
 extern irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data);
@@ -173,29 +160,5 @@ extern int pxa2xx_spi_set_dma_burst_and_threshold(struct chip_data *chip,
                                                  u8 bits_per_word,
                                                  u32 *burst_code,
                                                  u32 *threshold);
-#else
-static inline bool pxa2xx_spi_dma_is_possible(size_t len) { return false; }
-static inline int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data)
-{
-       return 0;
-}
-#define pxa2xx_spi_dma_transfer NULL
-static inline void pxa2xx_spi_dma_prepare(struct driver_data *drv_data,
-                                         u32 dma_burst) {}
-static inline void pxa2xx_spi_dma_start(struct driver_data *drv_data) {}
-static inline int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
-{
-       return 0;
-}
-static inline void pxa2xx_spi_dma_release(struct driver_data *drv_data) {}
-static inline int pxa2xx_spi_set_dma_burst_and_threshold(struct chip_data *chip,
-                                                        struct spi_device *spi,
-                                                        u8 bits_per_word,
-                                                        u32 *burst_code,
-                                                        u32 *threshold)
-{
-       return -ENODEV;
-}
-#endif
 
 #endif /* SPI_PXA2XX_H */
index b30f03a..de2f2f9 100644 (file)
@@ -144,6 +144,8 @@ SPI_STATISTICS_TRANSFER_BYTES_HISTO(14, "16384-32767");
 SPI_STATISTICS_TRANSFER_BYTES_HISTO(15, "32768-65535");
 SPI_STATISTICS_TRANSFER_BYTES_HISTO(16, "65536+");
 
+SPI_STATISTICS_SHOW(transfers_split_maxsize, "%lu");
+
 static struct attribute *spi_dev_attrs[] = {
        &dev_attr_modalias.attr,
        NULL,
@@ -181,6 +183,7 @@ static struct attribute *spi_device_statistics_attrs[] = {
        &dev_attr_spi_device_transfer_bytes_histo14.attr,
        &dev_attr_spi_device_transfer_bytes_histo15.attr,
        &dev_attr_spi_device_transfer_bytes_histo16.attr,
+       &dev_attr_spi_device_transfers_split_maxsize.attr,
        NULL,
 };
 
@@ -223,6 +226,7 @@ static struct attribute *spi_master_statistics_attrs[] = {
        &dev_attr_spi_master_transfer_bytes_histo14.attr,
        &dev_attr_spi_master_transfer_bytes_histo15.attr,
        &dev_attr_spi_master_transfer_bytes_histo16.attr,
+       &dev_attr_spi_master_transfers_split_maxsize.attr,
        NULL,
 };
 
@@ -1024,6 +1028,8 @@ out:
        if (msg->status && master->handle_err)
                master->handle_err(master, msg);
 
+       spi_res_release(master, msg);
+
        spi_finalize_current_message(master);
 
        return ret;
@@ -2043,6 +2049,336 @@ struct spi_master *spi_busnum_to_master(u16 bus_num)
 }
 EXPORT_SYMBOL_GPL(spi_busnum_to_master);
 
+/*-------------------------------------------------------------------------*/
+
+/* Core methods for SPI resource management */
+
+/**
+ * spi_res_alloc - allocate a spi resource that is life-cycle managed
+ *                 during the processing of a spi_message while using
+ *                 spi_transfer_one
+ * @spi:     the spi device for which we allocate memory
+ * @release: the release code to execute for this resource
+ * @size:    size to alloc and return
+ * @gfp:     GFP allocation flags
+ *
+ * Return: the pointer to the allocated data
+ *
+ * This may get enhanced in the future to allocate from a memory pool
+ * of the @spi_device or @spi_master to avoid repeated allocations.
+ */
+void *spi_res_alloc(struct spi_device *spi,
+                   spi_res_release_t release,
+                   size_t size, gfp_t gfp)
+{
+       struct spi_res *sres;
+
+       sres = kzalloc(sizeof(*sres) + size, gfp);
+       if (!sres)
+               return NULL;
+
+       INIT_LIST_HEAD(&sres->entry);
+       sres->release = release;
+
+       return sres->data;
+}
+EXPORT_SYMBOL_GPL(spi_res_alloc);
+
+/**
+ * spi_res_free - free an spi resource
+ * @res: pointer to the custom data of a resource
+ *
+ */
+void spi_res_free(void *res)
+{
+       struct spi_res *sres = container_of(res, struct spi_res, data);
+
+       if (!res)
+               return;
+
+       WARN_ON(!list_empty(&sres->entry));
+       kfree(sres);
+}
+EXPORT_SYMBOL_GPL(spi_res_free);
+
+/**
+ * spi_res_add - add a spi_res to the spi_message
+ * @message: the spi message
+ * @res:     the spi_resource
+ */
+void spi_res_add(struct spi_message *message, void *res)
+{
+       struct spi_res *sres = container_of(res, struct spi_res, data);
+
+       WARN_ON(!list_empty(&sres->entry));
+       list_add_tail(&sres->entry, &message->resources);
+}
+EXPORT_SYMBOL_GPL(spi_res_add);
+
+/**
+ * spi_res_release - release all spi resources for this message
+ * @master:  the @spi_master
+ * @message: the @spi_message
+ */
+void spi_res_release(struct spi_master *master,
+                    struct spi_message *message)
+{
+       struct spi_res *res;
+
+       while (!list_empty(&message->resources)) {
+               res = list_last_entry(&message->resources,
+                                     struct spi_res, entry);
+
+               if (res->release)
+                       res->release(master, message, res->data);
+
+               list_del(&res->entry);
+
+               kfree(res);
+       }
+}
+EXPORT_SYMBOL_GPL(spi_res_release);
+
+/*-------------------------------------------------------------------------*/
+
+/* Core methods for spi_message alterations */
+
+static void __spi_replace_transfers_release(struct spi_master *master,
+                                           struct spi_message *msg,
+                                           void *res)
+{
+       struct spi_replaced_transfers *rxfer = res;
+       size_t i;
+
+       /* call extra callback if requested */
+       if (rxfer->release)
+               rxfer->release(master, msg, res);
+
+       /* insert replaced transfers back into the message */
+       list_splice(&rxfer->replaced_transfers, rxfer->replaced_after);
+
+       /* remove the formerly inserted entries */
+       for (i = 0; i < rxfer->inserted; i++)
+               list_del(&rxfer->inserted_transfers[i].transfer_list);
+}
+
+/**
+ * spi_replace_transfers - replace transfers with several transfers
+ *                         and register change with spi_message.resources
+ * @msg:           the spi_message we work upon
+ * @xfer_first:    the first spi_transfer we want to replace
+ * @remove:        number of transfers to remove
+ * @insert:        the number of transfers we want to insert instead
+ * @release:       extra release code necessary in some circumstances
+ * @extradatasize: extra data to allocate (with alignment guarantees
+ *                 of struct @spi_transfer)
+ * @gfp:           gfp flags
+ *
+ * Returns: pointer to @spi_replaced_transfers,
+ *          PTR_ERR(...) in case of errors.
+ */
+struct spi_replaced_transfers *spi_replace_transfers(
+       struct spi_message *msg,
+       struct spi_transfer *xfer_first,
+       size_t remove,
+       size_t insert,
+       spi_replaced_release_t release,
+       size_t extradatasize,
+       gfp_t gfp)
+{
+       struct spi_replaced_transfers *rxfer;
+       struct spi_transfer *xfer;
+       size_t i;
+
+       /* allocate the structure using spi_res */
+       rxfer = spi_res_alloc(msg->spi, __spi_replace_transfers_release,
+                             insert * sizeof(struct spi_transfer)
+                             + sizeof(struct spi_replaced_transfers)
+                             + extradatasize,
+                             gfp);
+       if (!rxfer)
+               return ERR_PTR(-ENOMEM);
+
+       /* the release code to invoke before running the generic release */
+       rxfer->release = release;
+
+       /* assign extradata */
+       if (extradatasize)
+               rxfer->extradata =
+                       &rxfer->inserted_transfers[insert];
+
+       /* init the replaced_transfers list */
+       INIT_LIST_HEAD(&rxfer->replaced_transfers);
+
+       /* assign the list_entry after which we should reinsert
+        * the @replaced_transfers - it may be spi_message.messages!
+        */
+       rxfer->replaced_after = xfer_first->transfer_list.prev;
+
+       /* remove the requested number of transfers */
+       for (i = 0; i < remove; i++) {
+               /* if the entry after replaced_after it is msg->transfers
+                * then we have been requested to remove more transfers
+                * than are in the list
+                */
+               if (rxfer->replaced_after->next == &msg->transfers) {
+                       dev_err(&msg->spi->dev,
+                               "requested to remove more spi_transfers than are available\n");
+                       /* insert replaced transfers back into the message */
+                       list_splice(&rxfer->replaced_transfers,
+                                   rxfer->replaced_after);
+
+                       /* free the spi_replace_transfer structure */
+                       spi_res_free(rxfer);
+
+                       /* and return with an error */
+                       return ERR_PTR(-EINVAL);
+               }
+
+               /* remove the entry after replaced_after from list of
+                * transfers and add it to list of replaced_transfers
+                */
+               list_move_tail(rxfer->replaced_after->next,
+                              &rxfer->replaced_transfers);
+       }
+
+       /* create copy of the given xfer with identical settings
+        * based on the first transfer to get removed
+        */
+       for (i = 0; i < insert; i++) {
+               /* we need to run in reverse order */
+               xfer = &rxfer->inserted_transfers[insert - 1 - i];
+
+               /* copy all spi_transfer data */
+               memcpy(xfer, xfer_first, sizeof(*xfer));
+
+               /* add to list */
+               list_add(&xfer->transfer_list, rxfer->replaced_after);
+
+               /* clear cs_change and delay_usecs for all but the last */
+               if (i) {
+                       xfer->cs_change = false;
+                       xfer->delay_usecs = 0;
+               }
+       }
+
+       /* set up inserted */
+       rxfer->inserted = insert;
+
+       /* and register it with spi_res/spi_message */
+       spi_res_add(msg, rxfer);
+
+       return rxfer;
+}
+EXPORT_SYMBOL_GPL(spi_replace_transfers);
+
+static int __spi_split_transfer_maxsize(struct spi_master *master,
+                                       struct spi_message *msg,
+                                       struct spi_transfer **xferp,
+                                       size_t maxsize,
+                                       gfp_t gfp)
+{
+       struct spi_transfer *xfer = *xferp, *xfers;
+       struct spi_replaced_transfers *srt;
+       size_t offset;
+       size_t count, i;
+
+       /* warn once about this fact that we are splitting a transfer */
+       dev_warn_once(&msg->spi->dev,
+                     "spi_transfer of length %i exceed max length of %zu - needed to split transfers\n",
+                     xfer->len, maxsize);
+
+       /* calculate how many we have to replace */
+       count = DIV_ROUND_UP(xfer->len, maxsize);
+
+       /* create replacement */
+       srt = spi_replace_transfers(msg, xfer, 1, count, NULL, 0, gfp);
+       if (IS_ERR(srt))
+               return PTR_ERR(srt);
+       xfers = srt->inserted_transfers;
+
+       /* now handle each of those newly inserted spi_transfers
+        * note that the replacements spi_transfers all are preset
+        * to the same values as *xferp, so tx_buf, rx_buf and len
+        * are all identical (as well as most others)
+        * so we just have to fix up len and the pointers.
+        *
+        * this also includes support for the depreciated
+        * spi_message.is_dma_mapped interface
+        */
+
+       /* the first transfer just needs the length modified, so we
+        * run it outside the loop
+        */
+       xfers[0].len = min_t(size_t, maxsize, xfer[0].len);
+
+       /* all the others need rx_buf/tx_buf also set */
+       for (i = 1, offset = maxsize; i < count; offset += maxsize, i++) {
+               /* update rx_buf, tx_buf and dma */
+               if (xfers[i].rx_buf)
+                       xfers[i].rx_buf += offset;
+               if (xfers[i].rx_dma)
+                       xfers[i].rx_dma += offset;
+               if (xfers[i].tx_buf)
+                       xfers[i].tx_buf += offset;
+               if (xfers[i].tx_dma)
+                       xfers[i].tx_dma += offset;
+
+               /* update length */
+               xfers[i].len = min(maxsize, xfers[i].len - offset);
+       }
+
+       /* we set up xferp to the last entry we have inserted,
+        * so that we skip those already split transfers
+        */
+       *xferp = &xfers[count - 1];
+
+       /* increment statistics counters */
+       SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+                                      transfers_split_maxsize);
+       SPI_STATISTICS_INCREMENT_FIELD(&msg->spi->statistics,
+                                      transfers_split_maxsize);
+
+       return 0;
+}
+
+/**
+ * spi_split_tranfers_maxsize - split spi transfers into multiple transfers
+ *                              when an individual transfer exceeds a
+ *                              certain size
+ * @master:    the @spi_master for this transfer
+ * @msg:   the @spi_message to transform
+ * @maxsize:  the maximum when to apply this
+ * @gfp: GFP allocation flags
+ *
+ * Return: status of transformation
+ */
+int spi_split_transfers_maxsize(struct spi_master *master,
+                               struct spi_message *msg,
+                               size_t maxsize,
+                               gfp_t gfp)
+{
+       struct spi_transfer *xfer;
+       int ret;
+
+       /* iterate over the transfer_list,
+        * but note that xfer is advanced to the last transfer inserted
+        * to avoid checking sizes again unnecessarily (also xfer does
+        * potentiall belong to a different list by the time the
+        * replacement has happened
+        */
+       list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+               if (xfer->len > maxsize) {
+                       ret = __spi_split_transfer_maxsize(
+                               master, msg, &xfer, maxsize, gfp);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize);
 
 /*-------------------------------------------------------------------------*/
 
index c2f2574..2a097d1 100644 (file)
@@ -197,6 +197,7 @@ enum pxa_ssp_type {
        QUARK_X1000_SSP,
        LPSS_LPT_SSP, /* Keep LPSS types sorted with lpss_platforms[] */
        LPSS_BYT_SSP,
+       LPSS_BSW_SSP,
        LPSS_SPT_SSP,
        LPSS_BXT_SSP,
 };
index 06ea1c3..f54667b 100644 (file)
@@ -54,6 +54,10 @@ extern struct bus_type spi_bus_type;
  *
  * @transfer_bytes_histo:
  *                 transfer bytes histogramm
+ *
+ * @transfers_split_maxsize:
+ *                 number of transfers that have been split because of
+ *                 maxsize limit
  */
 struct spi_statistics {
        spinlock_t              lock; /* lock for the whole structure */
@@ -73,6 +77,8 @@ struct spi_statistics {
 
 #define SPI_STATISTICS_HISTO_SIZE 17
        unsigned long transfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];
+
+       unsigned long transfers_split_maxsize;
 };
 
 void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
@@ -594,6 +600,37 @@ extern void spi_unregister_master(struct spi_master *master);
 
 extern struct spi_master *spi_busnum_to_master(u16 busnum);
 
+/*
+ * SPI resource management while processing a SPI message
+ */
+
+/**
+ * struct spi_res - spi resource management structure
+ * @entry:   list entry
+ * @release: release code called prior to freeing this resource
+ * @data:    extra data allocated for the specific use-case
+ *
+ * this is based on ideas from devres, but focused on life-cycle
+ * management during spi_message processing
+ */
+typedef void (*spi_res_release_t)(struct spi_master *master,
+                                 struct spi_message *msg,
+                                 void *res);
+struct spi_res {
+       struct list_head        entry;
+       spi_res_release_t       release;
+       unsigned long long      data[]; /* guarantee ull alignment */
+};
+
+extern void *spi_res_alloc(struct spi_device *spi,
+                          spi_res_release_t release,
+                          size_t size, gfp_t gfp);
+extern void spi_res_add(struct spi_message *message, void *res);
+extern void spi_res_free(void *res);
+
+extern void spi_res_release(struct spi_master *master,
+                           struct spi_message *message);
+
 /*---------------------------------------------------------------------------*/
 
 /*
@@ -732,6 +769,7 @@ struct spi_transfer {
  * @status: zero for success, else negative errno
  * @queue: for use by whichever driver currently owns the message
  * @state: for use by whichever driver currently owns the message
+ * @resources: for resource management when the spi message is processed
  *
  * A @spi_message is used to execute an atomic sequence of data transfers,
  * each represented by a struct spi_transfer.  The sequence is "atomic"
@@ -778,11 +816,15 @@ struct spi_message {
         */
        struct list_head        queue;
        void                    *state;
+
+       /* list of spi_res reources when the spi message is processed */
+       struct list_head        resources;
 };
 
 static inline void spi_message_init_no_memset(struct spi_message *m)
 {
        INIT_LIST_HEAD(&m->transfers);
+       INIT_LIST_HEAD(&m->resources);
 }
 
 static inline void spi_message_init(struct spi_message *m)
@@ -866,6 +908,60 @@ spi_max_transfer_size(struct spi_device *spi)
 
 /*---------------------------------------------------------------------------*/
 
+/* SPI transfer replacement methods which make use of spi_res */
+
+struct spi_replaced_transfers;
+typedef void (*spi_replaced_release_t)(struct spi_master *master,
+                                      struct spi_message *msg,
+                                      struct spi_replaced_transfers *res);
+/**
+ * struct spi_replaced_transfers - structure describing the spi_transfer
+ *                                 replacements that have occurred
+ *                                 so that they can get reverted
+ * @release:            some extra release code to get executed prior to
+ *                      relasing this structure
+ * @extradata:          pointer to some extra data if requested or NULL
+ * @replaced_transfers: transfers that have been replaced and which need
+ *                      to get restored
+ * @replaced_after:     the transfer after which the @replaced_transfers
+ *                      are to get re-inserted
+ * @inserted:           number of transfers inserted
+ * @inserted_transfers: array of spi_transfers of array-size @inserted,
+ *                      that have been replacing replaced_transfers
+ *
+ * note: that @extradata will point to @inserted_transfers[@inserted]
+ * if some extra allocation is requested, so alignment will be the same
+ * as for spi_transfers
+ */
+struct spi_replaced_transfers {
+       spi_replaced_release_t release;
+       void *extradata;
+       struct list_head replaced_transfers;
+       struct list_head *replaced_after;
+       size_t inserted;
+       struct spi_transfer inserted_transfers[];
+};
+
+extern struct spi_replaced_transfers *spi_replace_transfers(
+       struct spi_message *msg,
+       struct spi_transfer *xfer_first,
+       size_t remove,
+       size_t insert,
+       spi_replaced_release_t release,
+       size_t extradatasize,
+       gfp_t gfp);
+
+/*---------------------------------------------------------------------------*/
+
+/* SPI transfer transformation methods */
+
+extern int spi_split_transfers_maxsize(struct spi_master *master,
+                                      struct spi_message *msg,
+                                      size_t maxsize,
+                                      gfp_t gfp);
+
+/*---------------------------------------------------------------------------*/
+
 /* All these synchronous SPI transfer routines are utilities layered
  * over the core async transfer primitive.  Here, "synchronous" means
  * they will sleep uninterruptibly until the async transfer completes.