Hi Jassi, On 19 January 2018 at 10:37, <jassisinghbrar@xxxxxxxxx> wrote: > From: Jassi Brar <jaswinder.singh@xxxxxxxxxx> > > This patch adds support for controller found on synquacer platforms. > > Signed-off-by: Jassi Brar <jaswinder.singh@xxxxxxxxxx> 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@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); > -- > 2.7.4 > -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html