Message ID | 1516358251-4523-1-git-send-email-jassisinghbrar@gmail.com |
---|---|
State | Superseded |
Headers | show |
Series | spi: support for Socionext Synquacer platform | expand |
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
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
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 --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");