diff mbox series

[PATCHv3,2/3] spi: Add spi driver for Socionext Synquacer platform

Message ID 1516358251-4523-1-git-send-email-jassisinghbrar@gmail.com
State Superseded
Headers show
Series spi: support for Socionext Synquacer platform | expand

Commit Message

Jassi Brar Jan. 19, 2018, 10:37 a.m. UTC
From: Jassi Brar <jaswinder.singh@linaro.org>


This patch adds support for controller found on synquacer platforms.

Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>

---
 drivers/spi/Kconfig         |  11 +
 drivers/spi/Makefile        |   1 +
 drivers/spi/spi-synquacer.c | 661 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 673 insertions(+)
 create mode 100644 drivers/spi/spi-synquacer.c

-- 
2.7.4

--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Ard Biesheuvel Feb. 8, 2018, 8:42 p.m. UTC | #1
Hi Jassi,

On 19 January 2018 at 10:37,  <jassisinghbrar@gmail.com> wrote:
> From: Jassi Brar <jaswinder.singh@linaro.org>

>

> This patch adds support for controller found on synquacer platforms.

>

> Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>


I added the following nodes to my SynQuacer boards:

    clk_fip006_spi: spi_ihclk {
        compatible = "fixed-clock";
        #clock-cells = <0>;
        clock-frequency = <125000000>;
    };

    spi@54810000 {
        compatible = "socionext,synquacer-spi";
        reg = <0x0 0x54810000 0x0 0x1000>;
        clocks = <&clk_fip006_spi>;
        clock-names = "iHCLK";
        socionext,use-rtm;
        socionext,set-aces;
        #address-cells = <1>;
        #size-cells = <0>;

        tpm@0 {
            reg = <0x0>;
            compatible = "infineon,slb9670";
            spi-max-frequency = <22500000>;
        };
    };

and I end up with the following splat:

[    5.741886] Unable to handle kernel paging request at virtual
address fffffffffffffffe
[    5.741889] Mem abort info:
[    5.741891]   ESR = 0x96000004
[    5.741895]   Exception class = DABT (current EL), IL = 32 bits
[    5.741898]   SET = 0, FnV = 0
[    5.741899]   EA = 0, S1PTW = 0
[    5.741901] Data abort info:
[    5.741903]   ISV = 0, ISS = 0x00000004
[    5.741905]   CM = 0, WnR = 0
[    5.741910] swapper pgtable: 4k pages, 48-bit VAs, pgd = 0000000042134b9d
[    5.741913] [fffffffffffffffe] *pgd=0000000000000000
[    5.741920] Internal error: Oops: 96000004 [#1] SMP
[    5.741924] Modules linked in: efivars(+) gpio_keys(+)
spi_synquacer(+) efivarfs ip_tables x_tables autofs4 ext4 crc16
mbcache jbd2 fscrypto sd_mod ahci xhci_pci libahci xhci_hcd libata
usbcore realtek scsi_mod netsec of_mdio fixed_phy libphy gpio_mb86s7x
[    5.741974] CPU: 18 PID: 389 Comm: systemd-udevd Not tainted 4.15.0+ #1
[    5.741976] Hardware name: Socionext Developer Box (DT)
[    5.741981] pstate: a0000005 (NzCv daif -PAN -UAO)
[    5.741995] pc : clk_prepare+0x1c/0x60
[    5.742007] lr : synquacer_spi_probe+0xe8/0x290 [spi_synquacer]
[    5.742008] sp : ffff00000d5739e0
[    5.742011] x29: ffff00000d5739e0 x28: ffff01911d855000
[    5.742016] x27: ffff3dda0e962000 x26: 0000000000000000
[    5.742021] x25: ffff01911d8542d0 x24: 0000000000000010
[    5.742026] x23: ffffbed27ffed5c8 x22: ffffbed25b7cb400
[    5.742031] x21: fffffffffffffffe x20: ffffbed25658b000
[    5.742036] x19: fffffffffffffffe x18: ffffffffffffffff
[    5.742041] x17: 0000ffff90f5ce58 x16: ffff3dda0e3cb818
[    5.742046] x15: ffff3dda0ee59c08 x14: ffffffffffffffff
[    5.742051] x13: 0000000000000028 x12: 0101010101010101
[    5.742055] x11: 0000000000000038 x10: 0101010101010101
[    5.742060] x9 : 0000000000000002 x8 : 7f7f7f7f7f7f7f7f
[    5.742065] x7 : ff6c73712c647274 x6 : 0000000000000080
[    5.742070] x5 : 0000000000000000 x4 : 8000000000000000
[    5.742075] x3 : 0000000000000000 x2 : 0000000000000000
[    5.742079] x1 : 0000000000000001 x0 : ffff01911d852778
[    5.742086] Process systemd-udevd (pid: 389, stack limit =
0x00000000cdd89d3b)
[    5.742088] Call trace:
[    5.742093]  clk_prepare+0x1c/0x60
[    5.742101]  synquacer_spi_probe+0xe8/0x290 [spi_synquacer]
[    5.742109]  platform_drv_probe+0x60/0xc8
[    5.742114]  driver_probe_device+0x2dc/0x480
[    5.742119]  __driver_attach+0x124/0x128
[    5.742124]  bus_for_each_dev+0x78/0xe0
[    5.742128]  driver_attach+0x30/0x40
[    5.742132]  bus_add_driver+0x1f8/0x2b0
[    5.742136]  driver_register+0x68/0x100
[    5.742141]  __platform_driver_register+0x54/0x60
[    5.742148]  synquacer_spi_driver_init+0x1c/0x1000 [spi_synquacer]
[    5.742154]  do_one_initcall+0x5c/0x168
[    5.742161]  do_init_module+0x64/0x1e0
[    5.742167]  load_module+0x1ed0/0x2198
[    5.742172]  SyS_finit_module+0x128/0x140
[    5.742176]  __sys_trace_return+0x0/0x4
[    5.742183] Code: aa0003f3 aa1e03e0 d503201f b4000193 (f9400273)
[    5.742188] ---[ end trace 831278301b1eda70 ]---


It looks like the call

clk_prepare_enable(sspi->clk[IPCLK]);

is passing the ERR() value of devm_clk_get() rather than NULL. Adding
'if (!IS_ERR())' fixes it for me.


> ---

>  drivers/spi/Kconfig         |  11 +

>  drivers/spi/Makefile        |   1 +

>  drivers/spi/spi-synquacer.c | 661 ++++++++++++++++++++++++++++++++++++++++++++

>  3 files changed, 673 insertions(+)

>  create mode 100644 drivers/spi/spi-synquacer.c

>

> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig

> index 6037839..9e04bbe 100644

> --- a/drivers/spi/Kconfig

> +++ b/drivers/spi/Kconfig

> @@ -659,6 +659,17 @@ config SPI_SUN6I

>         help

>           This enables using the SPI controller on the Allwinner A31 SoCs.

>

> +config SPI_SYNQUACER

> +       tristate "Socionext's Synquacer HighSpeed SPI controller"

> +       depends on ARCH_SYNQUACER || COMPILE_TEST

> +       select SPI_BITBANG

> +       help

> +         SPI driver for Socionext's High speed SPI controller which provides

> +         various operating modes for interfacing to serial peripheral devices

> +         that use the de-facto standard SPI protocol.

> +

> +         It also supports the new dual-bit and quad-bit SPI protocol.

> +

>  config SPI_MXS

>         tristate "Freescale MXS SPI controller"

>         depends on ARCH_MXS

> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile

> index 34c5f28..7c222f2 100644

> --- a/drivers/spi/Makefile

> +++ b/drivers/spi/Makefile

> @@ -96,6 +96,7 @@ obj-$(CONFIG_SPI_STM32)               += spi-stm32.o

>  obj-$(CONFIG_SPI_ST_SSC4)              += spi-st-ssc4.o

>  obj-$(CONFIG_SPI_SUN4I)                        += spi-sun4i.o

>  obj-$(CONFIG_SPI_SUN6I)                        += spi-sun6i.o

> +obj-$(CONFIG_SPI_SYNQUACER)            += spi-synquacer.o

>  obj-$(CONFIG_SPI_TEGRA114)             += spi-tegra114.o

>  obj-$(CONFIG_SPI_TEGRA20_SFLASH)       += spi-tegra20-sflash.o

>  obj-$(CONFIG_SPI_TEGRA20_SLINK)                += spi-tegra20-slink.o

> diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c

> new file mode 100644

> index 0000000..15568b1

> --- /dev/null

> +++ b/drivers/spi/spi-synquacer.c

> @@ -0,0 +1,661 @@

> +// SPDX-License-Identifier: GPL-2.0

> +//

> +// Synquacer HSSPI controller driver

> +//

> +// Copyright (c) 2015-2018 Socionext Inc.

> +// Copyright (c) 2018 Linaro Ltd.

> +//

> +

> +#include <linux/delay.h>

> +#include <linux/interrupt.h>

> +#include <linux/io.h>

> +#include <linux/module.h>

> +#include <linux/of.h>

> +#include <linux/platform_device.h>

> +#include <linux/pm_runtime.h>

> +#include <linux/scatterlist.h>

> +#include <linux/slab.h>

> +#include <linux/spi/spi.h>

> +#include <linux/spinlock.h>

> +#include <linux/clk.h>

> +#include <linux/clk-provider.h>

> +

> +#define MCTRL          0x0

> +#define MEN    BIT(0)

> +#define CSEN   BIT(1)

> +#define BPCLK  BIT(3)

> +#define MES    BIT(4)

> +#define SYNCON BIT(5)

> +

> +#define PCC0           0x4

> +#define PCC(n)         (PCC0 + (n) * 4)

> +#define RTM    BIT(3)

> +#define ACES   BIT(2)

> +#define SAFESYNC       BIT(16)

> +#define CPHA   BIT(0)

> +#define CPOL   BIT(1)

> +#define SSPOL  BIT(4)

> +#define SDIR   BIT(7)

> +#define SS2CD  5

> +#define SENDIAN        BIT(8)

> +#define CDRS_SHIFT     9

> +#define CDRS_MASK      0x7f

> +

> +#define TXF            0x14

> +#define TXE            0x18

> +#define TXC            0x1c

> +#define RXF            0x20

> +#define RXE            0x24

> +#define RXC            0x28

> +

> +#define FAULTF         0x2c

> +#define FAULTC         0x30

> +

> +#define DMCFG          0x34

> +#define SSDC           BIT(1)

> +#define MSTARTEN       BIT(2)

> +

> +#define        DMSTART         0x38

> +#define TRIGGER                BIT(0)

> +#define DMSTOP         BIT(8)

> +#define CS_MASK                3

> +#define CS_SHIFT       16

> +#define DATA_TXRX      0

> +#define DATA_RX                1

> +#define DATA_TX                2

> +#define DATA_MASK      3

> +#define DATA_SHIFT     26

> +#define BUS_WIDTH      24

> +

> +#define        DMBCC           0x3c

> +#define DMSTATUS       0x40

> +#define RX_DATA_MASK   0x1f

> +#define RX_DATA_SHIFT  8

> +#define TX_DATA_MASK   0x1f

> +#define TX_DATA_SHIFT  16

> +

> +#define TXBITCNT       0x44

> +

> +#define FIFOCFG                0x4c

> +#define BPW_MASK       0x3

> +#define BPW_SHIFT      8

> +#define RX_FLUSH       BIT(11)

> +#define TX_FLUSH       BIT(12)

> +#define RX_TRSHLD_MASK         0xf

> +#define RX_TRSHLD_SHIFT                0

> +#define TX_TRSHLD_MASK         0xf

> +#define TX_TRSHLD_SHIFT                4

> +

> +#define TXFIFO         0x50

> +#define RXFIFO         0x90

> +#define MID            0xfc

> +

> +#define FIFO_DEPTH     16

> +#define TX_TRSHLD      4

> +#define RX_TRSHLD      (FIFO_DEPTH - TX_TRSHLD)

> +

> +#define TXBIT  BIT(1)

> +#define RXBIT  BIT(2)

> +

> +#define IHCLK  0

> +#define IPCLK  1

> +

> +struct synquacer_spi {

> +       struct device *dev;

> +       struct spi_master *master;

> +

> +       unsigned int cs;

> +       unsigned int bpw;

> +       unsigned int mode;

> +       unsigned int speed;

> +       bool aces, rtm;

> +       void *rx_buf;

> +       const void *tx_buf;

> +       struct clk *clk[2];

> +       void __iomem *regs;

> +       unsigned int tx_words, rx_words;

> +       unsigned int bus_width;

> +};

> +

> +static void read_fifo(struct synquacer_spi *sspi)

> +{

> +       u32 len = readl_relaxed(sspi->regs + DMSTATUS);

> +       int i;

> +

> +       len = (len >> RX_DATA_SHIFT) & RX_DATA_MASK;

> +       len = min_t(unsigned int, len, sspi->rx_words);

> +

> +       switch (sspi->bpw) {

> +       case 8:

> +               {

> +               u8 *buf = sspi->rx_buf;

> +

> +               for (i = 0; i < len; i++)

> +                       *buf++ = readb_relaxed(sspi->regs + RXFIFO);

> +               sspi->rx_buf = buf;

> +               break;

> +               }

> +       case 16:

> +               {

> +               u16 *buf = sspi->rx_buf;

> +

> +               for (i = 0; i < len; i++)

> +                       *buf++ = readw_relaxed(sspi->regs + RXFIFO);

> +               sspi->rx_buf = buf;

> +               break;

> +               }

> +       default:

> +               {

> +               u32 *buf = sspi->rx_buf;

> +

> +               for (i = 0; i < len; i++)

> +                       *buf++ = readl_relaxed(sspi->regs + RXFIFO);

> +               sspi->rx_buf = buf;

> +               break;

> +               }

> +       }

> +

> +       sspi->rx_words -= len;

> +}

> +

> +static void write_fifo(struct synquacer_spi *sspi)

> +{

> +       u32 len = readl_relaxed(sspi->regs + DMSTATUS);

> +       int i;

> +

> +       len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;

> +       len = min_t(unsigned int, FIFO_DEPTH - len, sspi->tx_words);

> +

> +       switch (sspi->bpw) {

> +       case 8:

> +               {

> +               const u8 *buf = sspi->tx_buf;

> +

> +               for (i = 0; i < len; i++)

> +                       writeb_relaxed(*buf++, sspi->regs + TXFIFO);

> +               sspi->tx_buf = buf;

> +               break;

> +               }

> +       case 16:

> +               {

> +               const u16 *buf = sspi->tx_buf;

> +

> +               for (i = 0; i < len; i++)

> +                       writew_relaxed(*buf++, sspi->regs + TXFIFO);

> +               sspi->tx_buf = buf;

> +               break;

> +               }

> +       default:

> +               {

> +               const u32 *buf = sspi->tx_buf;

> +

> +               for (i = 0; i < len; i++)

> +                       writel_relaxed(*buf++, sspi->regs + TXFIFO);

> +               sspi->tx_buf = buf;

> +               break;

> +               }

> +       }

> +       sspi->tx_words -= len;

> +}

> +

> +static int synquacer_spi_config(struct spi_master *master,

> +                               struct spi_device *spi,

> +                               struct spi_transfer *xfer)

> +{

> +       struct synquacer_spi *sspi = spi_master_get_devdata(master);

> +       unsigned int speed, mode, bpw, cs, bus_width;

> +       unsigned long rate;

> +       u32 val, div;

> +

> +       /* Full Duplex only on 1bit wide bus */

> +       if (xfer->rx_buf && xfer->tx_buf &&

> +           (xfer->rx_nbits != 1 || xfer->tx_nbits != 1)) {

> +               dev_err(sspi->dev,

> +                       "RX and TX bus widths must match for Full-Duplex!\n");

> +               return -EINVAL;

> +       }

> +

> +       if (xfer->tx_buf)

> +               bus_width = xfer->tx_nbits;

> +       else

> +               bus_width = xfer->rx_nbits;

> +

> +       mode = spi->mode;

> +       cs = spi->chip_select;

> +       speed = xfer->speed_hz;

> +       bpw = xfer->bits_per_word;

> +

> +       /* return if nothing to change */

> +       if (speed == sspi->speed &&

> +           bus_width == sspi->bus_width && bpw == sspi->bpw &&

> +           mode == sspi->mode && cs == sspi->cs) {

> +               return 0;

> +       }

> +

> +       rate = master->max_speed_hz;

> +

> +       div = DIV_ROUND_UP(rate, speed);

> +       if (div > 254) {

> +               dev_err(sspi->dev, "Requested rate too low (%u)\n",

> +                       sspi->speed);

> +               return -EINVAL;

> +       }

> +

> +       val = readl_relaxed(sspi->regs + PCC(cs));

> +       val &= ~SAFESYNC;

> +       if (bpw == 8 && (mode & (SPI_TX_DUAL | SPI_RX_DUAL)) && div < 3)

> +               val |= SAFESYNC;

> +       if (bpw == 8 && (mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 6)

> +               val |= SAFESYNC;

> +       if (bpw == 16 && (mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 3)

> +               val |= SAFESYNC;

> +

> +       if (mode & SPI_CPHA)

> +               val |= CPHA;

> +       else

> +               val &= ~CPHA;

> +

> +       if (mode & SPI_CPOL)

> +               val |= CPOL;

> +       else

> +               val &= ~CPOL;

> +

> +       if (mode & SPI_CS_HIGH)

> +               val |= SSPOL;

> +       else

> +               val &= ~SSPOL;

> +

> +       if (mode & SPI_LSB_FIRST)

> +               val |= SDIR;

> +       else

> +               val &= ~SDIR;

> +

> +       if (sspi->aces)

> +               val |= ACES;

> +       else

> +               val &= ~ACES;

> +

> +       if (sspi->rtm)

> +               val |= RTM;

> +       else

> +               val &= ~RTM;

> +

> +       val |= (3 << SS2CD);

> +       val |= SENDIAN;

> +

> +       val &= ~(CDRS_MASK << CDRS_SHIFT);

> +       val |= ((div >> 1) << CDRS_SHIFT);

> +

> +       writel_relaxed(val, sspi->regs + PCC(cs));

> +

> +       val = readl_relaxed(sspi->regs + FIFOCFG);

> +       val &= ~(BPW_MASK << BPW_SHIFT);

> +       val |= ((bpw / 8 - 1) << BPW_SHIFT);

> +       writel_relaxed(val, sspi->regs + FIFOCFG);

> +

> +       val = readl_relaxed(sspi->regs + DMSTART);

> +       val &= ~(DATA_MASK << DATA_SHIFT);

> +

> +       if (xfer->tx_buf && xfer->rx_buf)

> +               val |= (DATA_TXRX << DATA_SHIFT);

> +       else if (xfer->rx_buf)

> +               val |= (DATA_RX << DATA_SHIFT);

> +       else

> +               val |= (DATA_TX << DATA_SHIFT);

> +

> +       val &= ~(3 << BUS_WIDTH);

> +       val |= ((bus_width >> 1) << BUS_WIDTH);

> +       writel_relaxed(val, sspi->regs + DMSTART);

> +

> +       sspi->bpw = bpw;

> +       sspi->mode = mode;

> +       sspi->speed = speed;

> +       sspi->cs = spi->chip_select;

> +       sspi->bus_width = bus_width;

> +

> +       return 0;

> +}

> +

> +static int synquacer_spi_transfer_one(struct spi_master *master,

> +                                     struct spi_device *spi,

> +                                     struct spi_transfer *xfer)

> +{

> +       struct synquacer_spi *sspi = spi_master_get_devdata(master);

> +       int ret, words, busy = 0;

> +       unsigned long bpw;

> +       u32 val;

> +

> +       val = readl_relaxed(sspi->regs + FIFOCFG);

> +       val |= RX_FLUSH;

> +       val |= TX_FLUSH;

> +       writel_relaxed(val, sspi->regs + FIFOCFG);

> +

> +       /* See if we can transfer 4-bytes as 1 word even if not asked */

> +       bpw = xfer->bits_per_word;

> +       if (bpw == 8 && !(xfer->len % 4) && !(spi->mode & SPI_LSB_FIRST))

> +               xfer->bits_per_word = 32;

> +

> +       ret = synquacer_spi_config(master, spi, xfer);

> +

> +       /* restore */

> +       xfer->bits_per_word = bpw;

> +

> +       if (ret)

> +               return ret;

> +

> +       sspi->tx_buf = xfer->tx_buf;

> +       sspi->rx_buf = xfer->rx_buf;

> +

> +       switch (sspi->bpw) {

> +       case 8:

> +               words = xfer->len;

> +               break;

> +       case 16:

> +               words = xfer->len / 2;

> +               break;

> +       default:

> +               words = xfer->len / 4;

> +               break;

> +       }

> +

> +       if (xfer->tx_buf) {

> +               busy |= TXBIT;

> +               sspi->tx_words = words;

> +       } else {

> +               sspi->tx_words = 0;

> +       }

> +

> +       if (xfer->rx_buf) {

> +               busy |= RXBIT;

> +               sspi->rx_words = words;

> +       } else {

> +               sspi->rx_words = 0;

> +       }

> +

> +       if (xfer->tx_buf)

> +               write_fifo(sspi);

> +

> +       if (xfer->rx_buf) {

> +               val = readl_relaxed(sspi->regs + FIFOCFG);

> +               val &= ~(RX_TRSHLD_MASK << RX_TRSHLD_SHIFT);

> +               val |= ((sspi->rx_words > FIFO_DEPTH ?

> +                       RX_TRSHLD : sspi->rx_words) << RX_TRSHLD_SHIFT);

> +               writel_relaxed(val, sspi->regs + FIFOCFG);

> +       }

> +

> +       writel_relaxed(~0, sspi->regs + TXC);

> +       writel_relaxed(~0, sspi->regs + RXC);

> +

> +       /* Trigger */

> +       val = readl_relaxed(sspi->regs + DMSTART);

> +       val |= TRIGGER;

> +       writel_relaxed(val, sspi->regs + DMSTART);

> +

> +       while (busy & (RXBIT | TXBIT)) {

> +               if (sspi->rx_words)

> +                       read_fifo(sspi);

> +               else

> +                       busy &= ~RXBIT;

> +

> +               if (sspi->tx_words) {

> +                       write_fifo(sspi);

> +               } else {

> +                       u32 len;

> +

> +                       do { /* wait for shifter to empty out */

> +                               cpu_relax();

> +                               len = readl_relaxed(sspi->regs + DMSTATUS);

> +                               len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;

> +                       } while (xfer->tx_buf && len);

> +                       busy &= ~TXBIT;

> +               }

> +       }

> +

> +       return 0;

> +}

> +

> +static void synquacer_spi_set_cs(struct spi_device *spi, bool enable)

> +{

> +       struct synquacer_spi *sspi = spi_master_get_devdata(spi->master);

> +       u32 val;

> +

> +       val = readl_relaxed(sspi->regs + DMSTART);

> +       val &= ~(CS_MASK << CS_SHIFT);

> +       val |= spi->chip_select << CS_SHIFT;

> +

> +       if (!enable) {

> +               writel_relaxed(val, sspi->regs + DMSTART);

> +

> +               val = readl_relaxed(sspi->regs + DMSTART);

> +               val &= ~DMSTOP;

> +               writel_relaxed(val, sspi->regs + DMSTART);

> +       } else {

> +               val |= DMSTOP;

> +               writel_relaxed(val, sspi->regs + DMSTART);

> +

> +               if (sspi->rx_buf) {

> +                       u32 buf[16];

> +

> +                       sspi->rx_buf = buf;

> +                       sspi->rx_words = 16;

> +                       read_fifo(sspi);

> +               }

> +       }

> +}

> +

> +static int synquacer_spi_enable(struct spi_master *master)

> +{

> +       struct synquacer_spi *sspi = spi_master_get_devdata(master);

> +       u32 val;

> +

> +       /* Disable module */

> +       writel_relaxed(0, sspi->regs + MCTRL);

> +       val = 0xfffff;

> +       while (--val && (readl_relaxed(sspi->regs + MCTRL) & MES))

> +               cpu_relax();

> +       if (!val)

> +               return -EBUSY;

> +

> +       writel_relaxed(0, sspi->regs + TXE);

> +       writel_relaxed(0, sspi->regs + RXE);

> +       val = readl_relaxed(sspi->regs + TXF);

> +       writel_relaxed(val, sspi->regs + TXC);

> +       val = readl_relaxed(sspi->regs + RXF);

> +       writel_relaxed(val, sspi->regs + RXC);

> +       val = readl_relaxed(sspi->regs + FAULTF);

> +       writel_relaxed(val, sspi->regs + FAULTC);

> +

> +       val = readl_relaxed(sspi->regs + DMCFG);

> +       val &= ~SSDC;

> +       val &= ~MSTARTEN;

> +       writel_relaxed(val, sspi->regs + DMCFG);

> +

> +       val = readl_relaxed(sspi->regs + MCTRL);

> +       if (IS_ERR(sspi->clk[IPCLK]))

> +               val &= ~BPCLK;

> +       else

> +               val |= BPCLK;

> +

> +       val &= ~CSEN;

> +       val |= MEN;

> +       val |= SYNCON;

> +       writel_relaxed(val, sspi->regs + MCTRL);

> +

> +       return 0;

> +}

> +

> +static int synquacer_spi_probe(struct platform_device *pdev)

> +{

> +       struct device_node *np = pdev->dev.of_node;

> +       struct spi_master *master;

> +       struct synquacer_spi *sspi;

> +       struct resource *res;

> +       int ret;

> +

> +       master = spi_alloc_master(&pdev->dev, sizeof(*sspi));

> +       if (!master)

> +               return -ENOMEM;

> +       platform_set_drvdata(pdev, master);

> +

> +       sspi = spi_master_get_devdata(master);

> +       sspi->dev = &pdev->dev;

> +       sspi->master = master;

> +

> +       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

> +       sspi->regs = devm_ioremap_resource(sspi->dev, res);

> +       if (IS_ERR(sspi->regs)) {

> +               ret = PTR_ERR(sspi->regs);

> +               goto put_spi;

> +       }

> +

> +       sspi->clk[IHCLK] = devm_clk_get(sspi->dev, "iHCLK");

> +       if (IS_ERR(sspi->clk[IHCLK])) {

> +               dev_err(&pdev->dev, "iHCLK not found\n");

> +               ret = PTR_ERR(sspi->clk[IHCLK]);

> +               goto put_spi;

> +       }

> +

> +       sspi->clk[IPCLK] = devm_clk_get(sspi->dev, "iPCLK");

> +

> +       sspi->aces = of_property_read_bool(np, "socionext,set-aces");

> +       sspi->rtm = of_property_read_bool(np, "socionext,use-rtm");

> +

> +       master->num_chipselect = 4; /* max 4 supported */

> +

> +       clk_prepare_enable(sspi->clk[IPCLK]);

> +       ret = clk_prepare_enable(sspi->clk[IHCLK]);

> +       if (ret)

> +               goto put_spi;

> +

> +       master->dev.of_node = np;

> +       master->auto_runtime_pm = true;

> +       master->bus_num = pdev->id;

> +

> +       master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_DUAL | SPI_RX_DUAL |

> +                               SPI_TX_QUAD | SPI_RX_QUAD;

> +       master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(24)

> +                                        | SPI_BPW_MASK(16) | SPI_BPW_MASK(8);

> +

> +       if (!IS_ERR(sspi->clk[IPCLK]))

> +               master->max_speed_hz = clk_get_rate(sspi->clk[IPCLK]);

> +       else

> +               master->max_speed_hz = clk_get_rate(sspi->clk[IHCLK]);

> +       master->min_speed_hz = master->max_speed_hz / 254;

> +

> +       master->set_cs = synquacer_spi_set_cs;

> +       master->transfer_one = synquacer_spi_transfer_one;

> +

> +       ret = synquacer_spi_enable(master);

> +       if (ret)

> +               goto fail_enable;

> +

> +       pm_runtime_set_active(sspi->dev);

> +       pm_runtime_enable(sspi->dev);

> +

> +       ret = devm_spi_register_master(sspi->dev, master);

> +       if (ret)

> +               goto disable_pm;

> +

> +       return 0;

> +

> +disable_pm:

> +       pm_runtime_disable(sspi->dev);

> +fail_enable:

> +       clk_disable_unprepare(sspi->clk[IHCLK]);

> +       clk_disable_unprepare(sspi->clk[IPCLK]);

> +put_spi:

> +       spi_master_put(master);

> +

> +       return ret;

> +}

> +

> +static int synquacer_spi_remove(struct platform_device *pdev)

> +{

> +       struct spi_master *master = platform_get_drvdata(pdev);

> +       struct synquacer_spi *sspi = spi_master_get_devdata(master);

> +

> +       pm_runtime_disable(sspi->dev);

> +       clk_disable_unprepare(sspi->clk[IHCLK]);

> +       clk_disable_unprepare(sspi->clk[IPCLK]);

> +       spi_master_put(master);

> +

> +       return 0;

> +}

> +

> +#ifdef CONFIG_PM_SLEEP

> +static int synquacer_spi_suspend(struct device *dev)

> +{

> +       struct spi_master *master = dev_get_drvdata(dev);

> +       struct synquacer_spi *sspi = spi_master_get_devdata(master);

> +       int ret;

> +

> +       ret = spi_master_suspend(master);

> +       if (ret)

> +               return ret;

> +

> +       if (!pm_runtime_suspended(dev)) {

> +               clk_disable_unprepare(sspi->clk[IPCLK]);

> +               clk_disable_unprepare(sspi->clk[IHCLK]);

> +       }

> +

> +       return ret;

> +}

> +

> +static int synquacer_spi_resume(struct device *dev)

> +{

> +       struct spi_master *master = dev_get_drvdata(dev);

> +       struct synquacer_spi *sspi = spi_master_get_devdata(master);

> +       int ret;

> +

> +       if (!pm_runtime_suspended(dev)) {

> +               /* Ensure reconfigure during next xfer */

> +               sspi->speed = 0;

> +

> +               clk_prepare_enable(sspi->clk[IPCLK]);

> +               ret = clk_prepare_enable(sspi->clk[IHCLK]);

> +               if (ret < 0) {

> +                       dev_err(dev, "failed to enable clk (%d)\n", ret);

> +                       return ret;

> +               }

> +

> +               ret = synquacer_spi_enable(master);

> +               if (ret) {

> +                       dev_err(dev, "failed to enable spi (%d)\n", ret);

> +                       return ret;

> +               }

> +       }

> +

> +       ret = spi_master_resume(master);

> +       if (ret < 0) {

> +               clk_disable_unprepare(sspi->clk[IHCLK]);

> +               clk_disable_unprepare(sspi->clk[IPCLK]);

> +       }

> +

> +       return ret;

> +}

> +#endif /* CONFIG_PM_SLEEP */

> +

> +static const struct dev_pm_ops synquacer_spi_pm_ops = {

> +       SET_SYSTEM_SLEEP_PM_OPS(synquacer_spi_suspend, synquacer_spi_resume)

> +};

> +

> +static const struct of_device_id synquacer_spi_of_match[] = {

> +       {.compatible = "socionext,synquacer-spi",},

> +       {},

> +};

> +MODULE_DEVICE_TABLE(of, synquacer_spi_of_match);

> +

> +static struct platform_driver synquacer_spi_driver = {

> +       .driver = {

> +               .name = "synquacer-spi",

> +               .pm = &synquacer_spi_pm_ops,

> +               .of_match_table = of_match_ptr(synquacer_spi_of_match),

> +       },

> +       .probe = synquacer_spi_probe,

> +       .remove = synquacer_spi_remove,

> +};

> +module_platform_driver(synquacer_spi_driver);

> +

> +MODULE_DESCRIPTION("Socionext Synquacer HS-SPI controller driver");

> +MODULE_AUTHOR("Jassi Brar <jaswinder.singh@linaro.org>");

> +MODULE_LICENSE("GPL v2");

> --

> 2.7.4

>

--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jassi Brar Feb. 9, 2018, 2:43 a.m. UTC | #2
On Fri, Feb 9, 2018 at 2:12 AM, Ard Biesheuvel
<ard.biesheuvel@linaro.org> wrote:
> On 19 January 2018 at 10:37,  <jassisinghbrar@gmail.com> wrote:

> > From: Jassi Brar <jaswinder.singh@linaro.org>

> >

> > This patch adds support for controller found on synquacer platforms.

> >

> > Signed-off-by: Jassi Brar <jaswinder.singh@linaro.org>

>

> I added the following nodes to my SynQuacer boards:

>

>     clk_fip006_spi: spi_ihclk {

>         compatible = "fixed-clock";

>         #clock-cells = <0>;

>         clock-frequency = <125000000>;

>     };

>

>     spi@54810000 {

>         compatible = "socionext,synquacer-spi";

>         reg = <0x0 0x54810000 0x0 0x1000>;

>         clocks = <&clk_fip006_spi>;

>         clock-names = "iHCLK";

>         socionext,use-rtm;

>         socionext,set-aces;

>         #address-cells = <1>;

>         #size-cells = <0>;

>

>         tpm@0 {

>             reg = <0x0>;

>             compatible = "infineon,slb9670";

>             spi-max-frequency = <22500000>;

>         };

>     };

>

> and I end up with the following splat:

>

> [    5.741886] Unable to handle kernel paging request at virtual

> address fffffffffffffffe

> [    5.741889] Mem abort info:

> [    5.741891]   ESR = 0x96000004

> [    5.741895]   Exception class = DABT (current EL), IL = 32 bits

> [    5.741898]   SET = 0, FnV = 0

> [    5.741899]   EA = 0, S1PTW = 0

> [    5.741901] Data abort info:

> [    5.741903]   ISV = 0, ISS = 0x00000004

> [    5.741905]   CM = 0, WnR = 0

> [    5.741910] swapper pgtable: 4k pages, 48-bit VAs, pgd = 0000000042134b9d

> [    5.741913] [fffffffffffffffe] *pgd=0000000000000000

> [    5.741920] Internal error: Oops: 96000004 [#1] SMP

> [    5.741924] Modules linked in: efivars(+) gpio_keys(+)

> spi_synquacer(+) efivarfs ip_tables x_tables autofs4 ext4 crc16

> mbcache jbd2 fscrypto sd_mod ahci xhci_pci libahci xhci_hcd libata

> usbcore realtek scsi_mod netsec of_mdio fixed_phy libphy gpio_mb86s7x

> [    5.741974] CPU: 18 PID: 389 Comm: systemd-udevd Not tainted 4.15.0+ #1

> [    5.741976] Hardware name: Socionext Developer Box (DT)

> [    5.741981] pstate: a0000005 (NzCv daif -PAN -UAO)

> [    5.741995] pc : clk_prepare+0x1c/0x60

> [    5.742007] lr : synquacer_spi_probe+0xe8/0x290 [spi_synquacer]

> [    5.742008] sp : ffff00000d5739e0

> [    5.742011] x29: ffff00000d5739e0 x28: ffff01911d855000

> [    5.742016] x27: ffff3dda0e962000 x26: 0000000000000000

> [    5.742021] x25: ffff01911d8542d0 x24: 0000000000000010

> [    5.742026] x23: ffffbed27ffed5c8 x22: ffffbed25b7cb400

> [    5.742031] x21: fffffffffffffffe x20: ffffbed25658b000

> [    5.742036] x19: fffffffffffffffe x18: ffffffffffffffff

> [    5.742041] x17: 0000ffff90f5ce58 x16: ffff3dda0e3cb818

> [    5.742046] x15: ffff3dda0ee59c08 x14: ffffffffffffffff

> [    5.742051] x13: 0000000000000028 x12: 0101010101010101

> [    5.742055] x11: 0000000000000038 x10: 0101010101010101

> [    5.742060] x9 : 0000000000000002 x8 : 7f7f7f7f7f7f7f7f

> [    5.742065] x7 : ff6c73712c647274 x6 : 0000000000000080

> [    5.742070] x5 : 0000000000000000 x4 : 8000000000000000

> [    5.742075] x3 : 0000000000000000 x2 : 0000000000000000

> [    5.742079] x1 : 0000000000000001 x0 : ffff01911d852778

> [    5.742086] Process systemd-udevd (pid: 389, stack limit =

> 0x00000000cdd89d3b)

> [    5.742088] Call trace:

> [    5.742093]  clk_prepare+0x1c/0x60

> [    5.742101]  synquacer_spi_probe+0xe8/0x290 [spi_synquacer]

> [    5.742109]  platform_drv_probe+0x60/0xc8

> [    5.742114]  driver_probe_device+0x2dc/0x480

> [    5.742119]  __driver_attach+0x124/0x128

> [    5.742124]  bus_for_each_dev+0x78/0xe0

> [    5.742128]  driver_attach+0x30/0x40

> [    5.742132]  bus_add_driver+0x1f8/0x2b0

> [    5.742136]  driver_register+0x68/0x100

> [    5.742141]  __platform_driver_register+0x54/0x60

> [    5.742148]  synquacer_spi_driver_init+0x1c/0x1000 [spi_synquacer]

> [    5.742154]  do_one_initcall+0x5c/0x168

> [    5.742161]  do_init_module+0x64/0x1e0

> [    5.742167]  load_module+0x1ed0/0x2198

> [    5.742172]  SyS_finit_module+0x128/0x140

> [    5.742176]  __sys_trace_return+0x0/0x4

> [    5.742183] Code: aa0003f3 aa1e03e0 d503201f b4000193 (f9400273)

> [    5.742188] ---[ end trace 831278301b1eda70 ]---

>

>

> It looks like the call

>

> clk_prepare_enable(sspi->clk[IPCLK]);

>

> is passing the ERR() value of devm_clk_get() rather than NULL. Adding

> 'if (!IS_ERR())' fixes it for me.

>

I had thought the clk_xyz() calls would immediately return if invalid
clock was provided, but I see they return only if the passed clock is
a NULL pointer, and not when its ERR_PTR. I think a better option
would be to make it NULL if devm_clk_get returns ERR_PTR.

Stephen, does it make sense to also catch the ERR_PTR in
clk_enable/prepare?   Like clk_disable, clk_unprepare already does.

Thanks.
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Stephen Boyd April 7, 2018, 12:51 p.m. UTC | #3
Quoting Jassi Brar (2018-02-08 18:43:49)
> On Fri, Feb 9, 2018 at 2:12 AM, Ard Biesheuvel

> <ard.biesheuvel@linaro.org> wrote:

> > On 19 January 2018 at 10:37,  <jassisinghbrar@gmail.com> wrote:

> >

> > It looks like the call

> >

> > clk_prepare_enable(sspi->clk[IPCLK]);

> >

> > is passing the ERR() value of devm_clk_get() rather than NULL. Adding

> > 'if (!IS_ERR())' fixes it for me.

> >

> I had thought the clk_xyz() calls would immediately return if invalid

> clock was provided, but I see they return only if the passed clock is

> a NULL pointer, and not when its ERR_PTR. I think a better option

> would be to make it NULL if devm_clk_get returns ERR_PTR.

> 

> Stephen, does it make sense to also catch the ERR_PTR in

> clk_enable/prepare?   Like clk_disable, clk_unprepare already does.

> 


No? Check return values instead.

The kernel did its job here and blew up with a nice stacktrace instead
of a one line error message or requiring us to add WARN_ON() to the clk
framework. We have the IS_ERR_OR_NULL checks in the disable and
unprepare paths to save some lines on error paths when clks are optional
and clk_get() returns an error pointer. We could change those checks if
we decide to introduce a clk_get_optional() API or something like that
which returns NULL pointers, but so far we haven't done that. Feel free
to propose such an API if you need it.
--
To unsubscribe from this list: send the line "unsubscribe linux-spi" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox series

Patch

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 6037839..9e04bbe 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -659,6 +659,17 @@  config SPI_SUN6I
 	help
 	  This enables using the SPI controller on the Allwinner A31 SoCs.
 
+config SPI_SYNQUACER
+	tristate "Socionext's Synquacer HighSpeed SPI controller"
+	depends on ARCH_SYNQUACER || COMPILE_TEST
+	select SPI_BITBANG
+	help
+	  SPI driver for Socionext's High speed SPI controller which provides
+	  various operating modes for interfacing to serial peripheral devices
+	  that use the de-facto standard SPI protocol.
+
+	  It also supports the new dual-bit and quad-bit SPI protocol.
+
 config SPI_MXS
 	tristate "Freescale MXS SPI controller"
 	depends on ARCH_MXS
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 34c5f28..7c222f2 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -96,6 +96,7 @@  obj-$(CONFIG_SPI_STM32) 		+= spi-stm32.o
 obj-$(CONFIG_SPI_ST_SSC4)		+= spi-st-ssc4.o
 obj-$(CONFIG_SPI_SUN4I)			+= spi-sun4i.o
 obj-$(CONFIG_SPI_SUN6I)			+= spi-sun6i.o
+obj-$(CONFIG_SPI_SYNQUACER)		+= spi-synquacer.o
 obj-$(CONFIG_SPI_TEGRA114)		+= spi-tegra114.o
 obj-$(CONFIG_SPI_TEGRA20_SFLASH)	+= spi-tegra20-sflash.o
 obj-$(CONFIG_SPI_TEGRA20_SLINK)		+= spi-tegra20-slink.o
diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c
new file mode 100644
index 0000000..15568b1
--- /dev/null
+++ b/drivers/spi/spi-synquacer.c
@@ -0,0 +1,661 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// Synquacer HSSPI controller driver
+//
+// Copyright (c) 2015-2018 Socionext Inc.
+// Copyright (c) 2018 Linaro Ltd.
+//
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+
+#define MCTRL		0x0
+#define MEN	BIT(0)
+#define CSEN	BIT(1)
+#define BPCLK	BIT(3)
+#define MES	BIT(4)
+#define SYNCON	BIT(5)
+
+#define PCC0		0x4
+#define PCC(n)		(PCC0 + (n) * 4)
+#define RTM	BIT(3)
+#define ACES	BIT(2)
+#define SAFESYNC	BIT(16)
+#define CPHA	BIT(0)
+#define CPOL	BIT(1)
+#define SSPOL	BIT(4)
+#define SDIR	BIT(7)
+#define SS2CD	5
+#define SENDIAN	BIT(8)
+#define CDRS_SHIFT	9
+#define CDRS_MASK	0x7f
+
+#define TXF		0x14
+#define TXE		0x18
+#define TXC		0x1c
+#define RXF		0x20
+#define RXE		0x24
+#define RXC		0x28
+
+#define FAULTF		0x2c
+#define FAULTC		0x30
+
+#define DMCFG		0x34
+#define SSDC		BIT(1)
+#define MSTARTEN	BIT(2)
+
+#define	DMSTART		0x38
+#define TRIGGER		BIT(0)
+#define DMSTOP		BIT(8)
+#define CS_MASK		3
+#define CS_SHIFT	16
+#define DATA_TXRX	0
+#define DATA_RX		1
+#define DATA_TX		2
+#define DATA_MASK	3
+#define DATA_SHIFT	26
+#define BUS_WIDTH	24
+
+#define	DMBCC		0x3c
+#define DMSTATUS	0x40
+#define RX_DATA_MASK	0x1f
+#define RX_DATA_SHIFT	8
+#define TX_DATA_MASK	0x1f
+#define TX_DATA_SHIFT	16
+
+#define TXBITCNT	0x44
+
+#define FIFOCFG		0x4c
+#define BPW_MASK	0x3
+#define BPW_SHIFT	8
+#define RX_FLUSH	BIT(11)
+#define TX_FLUSH	BIT(12)
+#define RX_TRSHLD_MASK		0xf
+#define RX_TRSHLD_SHIFT		0
+#define TX_TRSHLD_MASK		0xf
+#define TX_TRSHLD_SHIFT		4
+
+#define TXFIFO		0x50
+#define RXFIFO		0x90
+#define MID		0xfc
+
+#define FIFO_DEPTH	16
+#define TX_TRSHLD	4
+#define RX_TRSHLD	(FIFO_DEPTH - TX_TRSHLD)
+
+#define TXBIT	BIT(1)
+#define RXBIT	BIT(2)
+
+#define IHCLK	0
+#define IPCLK	1
+
+struct synquacer_spi {
+	struct device *dev;
+	struct spi_master *master;
+
+	unsigned int cs;
+	unsigned int bpw;
+	unsigned int mode;
+	unsigned int speed;
+	bool aces, rtm;
+	void *rx_buf;
+	const void *tx_buf;
+	struct clk *clk[2];
+	void __iomem *regs;
+	unsigned int tx_words, rx_words;
+	unsigned int bus_width;
+};
+
+static void read_fifo(struct synquacer_spi *sspi)
+{
+	u32 len = readl_relaxed(sspi->regs + DMSTATUS);
+	int i;
+
+	len = (len >> RX_DATA_SHIFT) & RX_DATA_MASK;
+	len = min_t(unsigned int, len, sspi->rx_words);
+
+	switch (sspi->bpw) {
+	case 8:
+		{
+		u8 *buf = sspi->rx_buf;
+
+		for (i = 0; i < len; i++)
+			*buf++ = readb_relaxed(sspi->regs + RXFIFO);
+		sspi->rx_buf = buf;
+		break;
+		}
+	case 16:
+		{
+		u16 *buf = sspi->rx_buf;
+
+		for (i = 0; i < len; i++)
+			*buf++ = readw_relaxed(sspi->regs + RXFIFO);
+		sspi->rx_buf = buf;
+		break;
+		}
+	default:
+		{
+		u32 *buf = sspi->rx_buf;
+
+		for (i = 0; i < len; i++)
+			*buf++ = readl_relaxed(sspi->regs + RXFIFO);
+		sspi->rx_buf = buf;
+		break;
+		}
+	}
+
+	sspi->rx_words -= len;
+}
+
+static void write_fifo(struct synquacer_spi *sspi)
+{
+	u32 len = readl_relaxed(sspi->regs + DMSTATUS);
+	int i;
+
+	len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;
+	len = min_t(unsigned int, FIFO_DEPTH - len, sspi->tx_words);
+
+	switch (sspi->bpw) {
+	case 8:
+		{
+		const u8 *buf = sspi->tx_buf;
+
+		for (i = 0; i < len; i++)
+			writeb_relaxed(*buf++, sspi->regs + TXFIFO);
+		sspi->tx_buf = buf;
+		break;
+		}
+	case 16:
+		{
+		const u16 *buf = sspi->tx_buf;
+
+		for (i = 0; i < len; i++)
+			writew_relaxed(*buf++, sspi->regs + TXFIFO);
+		sspi->tx_buf = buf;
+		break;
+		}
+	default:
+		{
+		const u32 *buf = sspi->tx_buf;
+
+		for (i = 0; i < len; i++)
+			writel_relaxed(*buf++, sspi->regs + TXFIFO);
+		sspi->tx_buf = buf;
+		break;
+		}
+	}
+	sspi->tx_words -= len;
+}
+
+static int synquacer_spi_config(struct spi_master *master,
+				struct spi_device *spi,
+				struct spi_transfer *xfer)
+{
+	struct synquacer_spi *sspi = spi_master_get_devdata(master);
+	unsigned int speed, mode, bpw, cs, bus_width;
+	unsigned long rate;
+	u32 val, div;
+
+	/* Full Duplex only on 1bit wide bus */
+	if (xfer->rx_buf && xfer->tx_buf &&
+	    (xfer->rx_nbits != 1 || xfer->tx_nbits != 1)) {
+		dev_err(sspi->dev,
+			"RX and TX bus widths must match for Full-Duplex!\n");
+		return -EINVAL;
+	}
+
+	if (xfer->tx_buf)
+		bus_width = xfer->tx_nbits;
+	else
+		bus_width = xfer->rx_nbits;
+
+	mode = spi->mode;
+	cs = spi->chip_select;
+	speed = xfer->speed_hz;
+	bpw = xfer->bits_per_word;
+
+	/* return if nothing to change */
+	if (speed == sspi->speed &&
+	    bus_width == sspi->bus_width && bpw == sspi->bpw &&
+	    mode == sspi->mode && cs == sspi->cs) {
+		return 0;
+	}
+
+	rate = master->max_speed_hz;
+
+	div = DIV_ROUND_UP(rate, speed);
+	if (div > 254) {
+		dev_err(sspi->dev, "Requested rate too low (%u)\n",
+			sspi->speed);
+		return -EINVAL;
+	}
+
+	val = readl_relaxed(sspi->regs + PCC(cs));
+	val &= ~SAFESYNC;
+	if (bpw == 8 &&	(mode & (SPI_TX_DUAL | SPI_RX_DUAL)) && div < 3)
+		val |= SAFESYNC;
+	if (bpw == 8 &&	(mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 6)
+		val |= SAFESYNC;
+	if (bpw == 16 && (mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 3)
+		val |= SAFESYNC;
+
+	if (mode & SPI_CPHA)
+		val |= CPHA;
+	else
+		val &= ~CPHA;
+
+	if (mode & SPI_CPOL)
+		val |= CPOL;
+	else
+		val &= ~CPOL;
+
+	if (mode & SPI_CS_HIGH)
+		val |= SSPOL;
+	else
+		val &= ~SSPOL;
+
+	if (mode & SPI_LSB_FIRST)
+		val |= SDIR;
+	else
+		val &= ~SDIR;
+
+	if (sspi->aces)
+		val |= ACES;
+	else
+		val &= ~ACES;
+
+	if (sspi->rtm)
+		val |= RTM;
+	else
+		val &= ~RTM;
+
+	val |= (3 << SS2CD);
+	val |= SENDIAN;
+
+	val &= ~(CDRS_MASK << CDRS_SHIFT);
+	val |= ((div >> 1) << CDRS_SHIFT);
+
+	writel_relaxed(val, sspi->regs + PCC(cs));
+
+	val = readl_relaxed(sspi->regs + FIFOCFG);
+	val &= ~(BPW_MASK << BPW_SHIFT);
+	val |= ((bpw / 8 - 1) << BPW_SHIFT);
+	writel_relaxed(val, sspi->regs + FIFOCFG);
+
+	val = readl_relaxed(sspi->regs + DMSTART);
+	val &= ~(DATA_MASK << DATA_SHIFT);
+
+	if (xfer->tx_buf && xfer->rx_buf)
+		val |= (DATA_TXRX << DATA_SHIFT);
+	else if (xfer->rx_buf)
+		val |= (DATA_RX << DATA_SHIFT);
+	else
+		val |= (DATA_TX << DATA_SHIFT);
+
+	val &= ~(3 << BUS_WIDTH);
+	val |= ((bus_width >> 1) << BUS_WIDTH);
+	writel_relaxed(val, sspi->regs + DMSTART);
+
+	sspi->bpw = bpw;
+	sspi->mode = mode;
+	sspi->speed = speed;
+	sspi->cs = spi->chip_select;
+	sspi->bus_width = bus_width;
+
+	return 0;
+}
+
+static int synquacer_spi_transfer_one(struct spi_master *master,
+				      struct spi_device *spi,
+				      struct spi_transfer *xfer)
+{
+	struct synquacer_spi *sspi = spi_master_get_devdata(master);
+	int ret, words, busy = 0;
+	unsigned long bpw;
+	u32 val;
+
+	val = readl_relaxed(sspi->regs + FIFOCFG);
+	val |= RX_FLUSH;
+	val |= TX_FLUSH;
+	writel_relaxed(val, sspi->regs + FIFOCFG);
+
+	/* See if we can transfer 4-bytes as 1 word even if not asked */
+	bpw = xfer->bits_per_word;
+	if (bpw == 8 && !(xfer->len % 4) && !(spi->mode & SPI_LSB_FIRST))
+		xfer->bits_per_word = 32;
+
+	ret = synquacer_spi_config(master, spi, xfer);
+
+	/* restore */
+	xfer->bits_per_word = bpw;
+
+	if (ret)
+		return ret;
+
+	sspi->tx_buf = xfer->tx_buf;
+	sspi->rx_buf = xfer->rx_buf;
+
+	switch (sspi->bpw) {
+	case 8:
+		words = xfer->len;
+		break;
+	case 16:
+		words = xfer->len / 2;
+		break;
+	default:
+		words = xfer->len / 4;
+		break;
+	}
+
+	if (xfer->tx_buf) {
+		busy |= TXBIT;
+		sspi->tx_words = words;
+	} else {
+		sspi->tx_words = 0;
+	}
+
+	if (xfer->rx_buf) {
+		busy |= RXBIT;
+		sspi->rx_words = words;
+	} else {
+		sspi->rx_words = 0;
+	}
+
+	if (xfer->tx_buf)
+		write_fifo(sspi);
+
+	if (xfer->rx_buf) {
+		val = readl_relaxed(sspi->regs + FIFOCFG);
+		val &= ~(RX_TRSHLD_MASK << RX_TRSHLD_SHIFT);
+		val |= ((sspi->rx_words > FIFO_DEPTH ?
+			RX_TRSHLD : sspi->rx_words) << RX_TRSHLD_SHIFT);
+		writel_relaxed(val, sspi->regs + FIFOCFG);
+	}
+
+	writel_relaxed(~0, sspi->regs + TXC);
+	writel_relaxed(~0, sspi->regs + RXC);
+
+	/* Trigger */
+	val = readl_relaxed(sspi->regs + DMSTART);
+	val |= TRIGGER;
+	writel_relaxed(val, sspi->regs + DMSTART);
+
+	while (busy & (RXBIT | TXBIT)) {
+		if (sspi->rx_words)
+			read_fifo(sspi);
+		else
+			busy &= ~RXBIT;
+
+		if (sspi->tx_words) {
+			write_fifo(sspi);
+		} else {
+			u32 len;
+
+			do { /* wait for shifter to empty out */
+				cpu_relax();
+				len = readl_relaxed(sspi->regs + DMSTATUS);
+				len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;
+			} while (xfer->tx_buf && len);
+			busy &= ~TXBIT;
+		}
+	}
+
+	return 0;
+}
+
+static void synquacer_spi_set_cs(struct spi_device *spi, bool enable)
+{
+	struct synquacer_spi *sspi = spi_master_get_devdata(spi->master);
+	u32 val;
+
+	val = readl_relaxed(sspi->regs + DMSTART);
+	val &= ~(CS_MASK << CS_SHIFT);
+	val |= spi->chip_select << CS_SHIFT;
+
+	if (!enable) {
+		writel_relaxed(val, sspi->regs + DMSTART);
+
+		val = readl_relaxed(sspi->regs + DMSTART);
+		val &= ~DMSTOP;
+		writel_relaxed(val, sspi->regs + DMSTART);
+	} else {
+		val |= DMSTOP;
+		writel_relaxed(val, sspi->regs + DMSTART);
+
+		if (sspi->rx_buf) {
+			u32 buf[16];
+
+			sspi->rx_buf = buf;
+			sspi->rx_words = 16;
+			read_fifo(sspi);
+		}
+	}
+}
+
+static int synquacer_spi_enable(struct spi_master *master)
+{
+	struct synquacer_spi *sspi = spi_master_get_devdata(master);
+	u32 val;
+
+	/* Disable module */
+	writel_relaxed(0, sspi->regs + MCTRL);
+	val = 0xfffff;
+	while (--val && (readl_relaxed(sspi->regs + MCTRL) & MES))
+		cpu_relax();
+	if (!val)
+		return -EBUSY;
+
+	writel_relaxed(0, sspi->regs + TXE);
+	writel_relaxed(0, sspi->regs + RXE);
+	val = readl_relaxed(sspi->regs + TXF);
+	writel_relaxed(val, sspi->regs + TXC);
+	val = readl_relaxed(sspi->regs + RXF);
+	writel_relaxed(val, sspi->regs + RXC);
+	val = readl_relaxed(sspi->regs + FAULTF);
+	writel_relaxed(val, sspi->regs + FAULTC);
+
+	val = readl_relaxed(sspi->regs + DMCFG);
+	val &= ~SSDC;
+	val &= ~MSTARTEN;
+	writel_relaxed(val, sspi->regs + DMCFG);
+
+	val = readl_relaxed(sspi->regs + MCTRL);
+	if (IS_ERR(sspi->clk[IPCLK]))
+		val &= ~BPCLK;
+	else
+		val |= BPCLK;
+
+	val &= ~CSEN;
+	val |= MEN;
+	val |= SYNCON;
+	writel_relaxed(val, sspi->regs + MCTRL);
+
+	return 0;
+}
+
+static int synquacer_spi_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct spi_master *master;
+	struct synquacer_spi *sspi;
+	struct resource *res;
+	int ret;
+
+	master = spi_alloc_master(&pdev->dev, sizeof(*sspi));
+	if (!master)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, master);
+
+	sspi = spi_master_get_devdata(master);
+	sspi->dev = &pdev->dev;
+	sspi->master = master;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	sspi->regs = devm_ioremap_resource(sspi->dev, res);
+	if (IS_ERR(sspi->regs)) {
+		ret = PTR_ERR(sspi->regs);
+		goto put_spi;
+	}
+
+	sspi->clk[IHCLK] = devm_clk_get(sspi->dev, "iHCLK");
+	if (IS_ERR(sspi->clk[IHCLK])) {
+		dev_err(&pdev->dev, "iHCLK not found\n");
+		ret = PTR_ERR(sspi->clk[IHCLK]);
+		goto put_spi;
+	}
+
+	sspi->clk[IPCLK] = devm_clk_get(sspi->dev, "iPCLK");
+
+	sspi->aces = of_property_read_bool(np, "socionext,set-aces");
+	sspi->rtm = of_property_read_bool(np, "socionext,use-rtm");
+
+	master->num_chipselect = 4; /* max 4 supported */
+
+	clk_prepare_enable(sspi->clk[IPCLK]);
+	ret = clk_prepare_enable(sspi->clk[IHCLK]);
+	if (ret)
+		goto put_spi;
+
+	master->dev.of_node = np;
+	master->auto_runtime_pm = true;
+	master->bus_num = pdev->id;
+
+	master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_DUAL | SPI_RX_DUAL |
+				SPI_TX_QUAD | SPI_RX_QUAD;
+	master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(24)
+					 | SPI_BPW_MASK(16) | SPI_BPW_MASK(8);
+
+	if (!IS_ERR(sspi->clk[IPCLK]))
+		master->max_speed_hz = clk_get_rate(sspi->clk[IPCLK]);
+	else
+		master->max_speed_hz = clk_get_rate(sspi->clk[IHCLK]);
+	master->min_speed_hz = master->max_speed_hz / 254;
+
+	master->set_cs = synquacer_spi_set_cs;
+	master->transfer_one = synquacer_spi_transfer_one;
+
+	ret = synquacer_spi_enable(master);
+	if (ret)
+		goto fail_enable;
+
+	pm_runtime_set_active(sspi->dev);
+	pm_runtime_enable(sspi->dev);
+
+	ret = devm_spi_register_master(sspi->dev, master);
+	if (ret)
+		goto disable_pm;
+
+	return 0;
+
+disable_pm:
+	pm_runtime_disable(sspi->dev);
+fail_enable:
+	clk_disable_unprepare(sspi->clk[IHCLK]);
+	clk_disable_unprepare(sspi->clk[IPCLK]);
+put_spi:
+	spi_master_put(master);
+
+	return ret;
+}
+
+static int synquacer_spi_remove(struct platform_device *pdev)
+{
+	struct spi_master *master = platform_get_drvdata(pdev);
+	struct synquacer_spi *sspi = spi_master_get_devdata(master);
+
+	pm_runtime_disable(sspi->dev);
+	clk_disable_unprepare(sspi->clk[IHCLK]);
+	clk_disable_unprepare(sspi->clk[IPCLK]);
+	spi_master_put(master);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int synquacer_spi_suspend(struct device *dev)
+{
+	struct spi_master *master = dev_get_drvdata(dev);
+	struct synquacer_spi *sspi = spi_master_get_devdata(master);
+	int ret;
+
+	ret = spi_master_suspend(master);
+	if (ret)
+		return ret;
+
+	if (!pm_runtime_suspended(dev)) {
+		clk_disable_unprepare(sspi->clk[IPCLK]);
+		clk_disable_unprepare(sspi->clk[IHCLK]);
+	}
+
+	return ret;
+}
+
+static int synquacer_spi_resume(struct device *dev)
+{
+	struct spi_master *master = dev_get_drvdata(dev);
+	struct synquacer_spi *sspi = spi_master_get_devdata(master);
+	int ret;
+
+	if (!pm_runtime_suspended(dev)) {
+		/* Ensure reconfigure during next xfer */
+		sspi->speed = 0;
+
+		clk_prepare_enable(sspi->clk[IPCLK]);
+		ret = clk_prepare_enable(sspi->clk[IHCLK]);
+		if (ret < 0) {
+			dev_err(dev, "failed to enable clk (%d)\n", ret);
+			return ret;
+		}
+
+		ret = synquacer_spi_enable(master);
+		if (ret) {
+			dev_err(dev, "failed to enable spi (%d)\n", ret);
+			return ret;
+		}
+	}
+
+	ret = spi_master_resume(master);
+	if (ret < 0) {
+		clk_disable_unprepare(sspi->clk[IHCLK]);
+		clk_disable_unprepare(sspi->clk[IPCLK]);
+	}
+
+	return ret;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops synquacer_spi_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(synquacer_spi_suspend, synquacer_spi_resume)
+};
+
+static const struct of_device_id synquacer_spi_of_match[] = {
+	{.compatible = "socionext,synquacer-spi",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, synquacer_spi_of_match);
+
+static struct platform_driver synquacer_spi_driver = {
+	.driver = {
+		.name = "synquacer-spi",
+		.pm = &synquacer_spi_pm_ops,
+		.of_match_table = of_match_ptr(synquacer_spi_of_match),
+	},
+	.probe = synquacer_spi_probe,
+	.remove = synquacer_spi_remove,
+};
+module_platform_driver(synquacer_spi_driver);
+
+MODULE_DESCRIPTION("Socionext Synquacer HS-SPI controller driver");
+MODULE_AUTHOR("Jassi Brar <jaswinder.singh@linaro.org>");
+MODULE_LICENSE("GPL v2");