From 409851c38c742e56747b1911a5091e22b6c8514f Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 9 Oct 2014 11:27:45 +0800 Subject: spi: fsl-dspi: remove useless code for dspi driver. Since we are using regmap framework's internal locks, so the lock_arg for dspi_regmap_config is redundant here. This patch just remove it, and then the dspi_regmap_config could be const type. Signed-off-by: Xiubo Li Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-dspi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-fsl-dspi.c b/drivers/spi/spi-fsl-dspi.c index 448216025ce8..12dc99546394 100644 --- a/drivers/spi/spi-fsl-dspi.c +++ b/drivers/spi/spi-fsl-dspi.c @@ -438,7 +438,7 @@ static int dspi_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(dspi_pm, dspi_suspend, dspi_resume); -static struct regmap_config dspi_regmap_config = { +static const struct regmap_config dspi_regmap_config = { .reg_bits = 32, .val_bits = 32, .reg_stride = 4, @@ -492,7 +492,6 @@ static int dspi_probe(struct platform_device *pdev) goto out_master_put; } - dspi_regmap_config.lock_arg = dspi; dspi->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "dspi", base, &dspi_regmap_config); if (IS_ERR(dspi->regmap)) { -- cgit v1.2.2 From d1d81802522ade84128a2c66c0d500e372474dca Mon Sep 17 00:00:00 2001 From: Torsten Fleischer Date: Mon, 3 Nov 2014 17:17:55 +0100 Subject: spi: spi-gpio: Add dt support for a single device with no chip select In order to describe a single slave device that has no chip select line the 'num-chipselects' property has to be <0> and the 'cs-gpios' property doesn't need to be set. Signed-off-by: Torsten Fleischer Signed-off-by: Mark Brown --- drivers/spi/spi-gpio.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c index 9f595535cf27..f0492c99d145 100644 --- a/drivers/spi/spi-gpio.c +++ b/drivers/spi/spi-gpio.c @@ -413,6 +413,7 @@ static int spi_gpio_probe(struct platform_device *pdev) struct spi_gpio_platform_data *pdata; u16 master_flags = 0; bool use_of = 0; + int num_devices; status = spi_gpio_probe_dt(pdev); if (status < 0) @@ -422,16 +423,21 @@ static int spi_gpio_probe(struct platform_device *pdev) pdata = dev_get_platdata(&pdev->dev); #ifdef GENERIC_BITBANG - if (!pdata || !pdata->num_chipselect) + if (!pdata || (!use_of && !pdata->num_chipselect)) return -ENODEV; #endif + if (use_of && !SPI_N_CHIPSEL) + num_devices = 1; + else + num_devices = SPI_N_CHIPSEL; + status = spi_gpio_request(pdata, dev_name(&pdev->dev), &master_flags); if (status < 0) return status; master = spi_alloc_master(&pdev->dev, sizeof(*spi_gpio) + - (sizeof(int) * SPI_N_CHIPSEL)); + (sizeof(int) * num_devices)); if (!master) { status = -ENOMEM; goto gpio_free; @@ -446,7 +452,7 @@ static int spi_gpio_probe(struct platform_device *pdev) master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32); master->flags = master_flags; master->bus_num = pdev->id; - master->num_chipselect = SPI_N_CHIPSEL; + master->num_chipselect = num_devices; master->setup = spi_gpio_setup; master->cleanup = spi_gpio_cleanup; #ifdef CONFIG_OF @@ -461,9 +467,12 @@ static int spi_gpio_probe(struct platform_device *pdev) * property of the node. */ - for (i = 0; i < SPI_N_CHIPSEL; i++) - spi_gpio->cs_gpios[i] = - of_get_named_gpio(np, "cs-gpios", i); + if (!SPI_N_CHIPSEL) + spi_gpio->cs_gpios[0] = SPI_GPIO_NO_CHIPSELECT; + else + for (i = 0; i < SPI_N_CHIPSEL; i++) + spi_gpio->cs_gpios[i] = + of_get_named_gpio(np, "cs-gpios", i); } #endif -- cgit v1.2.2 From cfb4bbd8fbed76a6b715916fabeca4597cb38721 Mon Sep 17 00:00:00 2001 From: Torsten Fleischer Date: Fri, 7 Nov 2014 18:11:58 +0100 Subject: spi: spi-gpio: Fix compiler warning when building for 64 bit systems The assignment of SPI_GPIO_NO_CHIPSELECT to cs_gpios[0] causes the following compiler warning, when building for 64 bit systems: "warning: overflow in implicit constant conversion [-Woverflow]". This is because the SPI_GPIO_NO_CHIPSELECT flag is a '-1' type casted to unsigned long and cs_gpios is of the type int. Furthermore the chip select's GPIO number is locally stored as unsigned int and compared with SPI_GPIO_NO_CHIPSELECT. Thus the result of the comparison is always false, if unsigned long and unsigned int have a different size. As part of the fix this patch adds a check for the device tree's cs-gpios property. Reported-by: kbuild test robot Signed-off-by: Torsten Fleischer Signed-off-by: Mark Brown --- drivers/spi/spi-gpio.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c index f0492c99d145..4b600d4f8548 100644 --- a/drivers/spi/spi-gpio.c +++ b/drivers/spi/spi-gpio.c @@ -48,7 +48,7 @@ struct spi_gpio { struct spi_bitbang bitbang; struct spi_gpio_platform_data pdata; struct platform_device *pdev; - int cs_gpios[0]; + unsigned long cs_gpios[0]; }; /*----------------------------------------------------------------------*/ @@ -220,7 +220,7 @@ static u32 spi_gpio_spec_txrx_word_mode3(struct spi_device *spi, static void spi_gpio_chipselect(struct spi_device *spi, int is_active) { struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi); - unsigned int cs = spi_gpio->cs_gpios[spi->chip_select]; + unsigned long cs = spi_gpio->cs_gpios[spi->chip_select]; /* set initial clock polarity */ if (is_active) @@ -234,7 +234,7 @@ static void spi_gpio_chipselect(struct spi_device *spi, int is_active) static int spi_gpio_setup(struct spi_device *spi) { - unsigned int cs; + unsigned long cs; int status = 0; struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi); struct device_node *np = spi->master->dev.of_node; @@ -249,7 +249,7 @@ static int spi_gpio_setup(struct spi_device *spi) /* * ... otherwise, take it from spi->controller_data */ - cs = (unsigned int)(uintptr_t) spi->controller_data; + cs = (uintptr_t) spi->controller_data; } if (!spi->controller_state) { @@ -277,7 +277,7 @@ static int spi_gpio_setup(struct spi_device *spi) static void spi_gpio_cleanup(struct spi_device *spi) { struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi); - unsigned int cs = spi_gpio->cs_gpios[spi->chip_select]; + unsigned long cs = spi_gpio->cs_gpios[spi->chip_select]; if (cs != SPI_GPIO_NO_CHIPSELECT) gpio_free(cs); @@ -437,7 +437,7 @@ static int spi_gpio_probe(struct platform_device *pdev) return status; master = spi_alloc_master(&pdev->dev, sizeof(*spi_gpio) + - (sizeof(int) * num_devices)); + (sizeof(unsigned long) * num_devices)); if (!master) { status = -ENOMEM; goto gpio_free; @@ -470,9 +470,15 @@ static int spi_gpio_probe(struct platform_device *pdev) if (!SPI_N_CHIPSEL) spi_gpio->cs_gpios[0] = SPI_GPIO_NO_CHIPSELECT; else - for (i = 0; i < SPI_N_CHIPSEL; i++) - spi_gpio->cs_gpios[i] = - of_get_named_gpio(np, "cs-gpios", i); + for (i = 0; i < SPI_N_CHIPSEL; i++) { + status = of_get_named_gpio(np, "cs-gpios", i); + if (status < 0) { + dev_err(&pdev->dev, + "invalid cs-gpios property\n"); + goto gpio_free; + } + spi_gpio->cs_gpios[i] = status; + } } #endif -- cgit v1.2.2 From deba25800a12b99e1a68bc83d33709be4b9a40b7 Mon Sep 17 00:00:00 2001 From: Andrew Bresticker Date: Fri, 14 Nov 2014 10:48:32 -0800 Subject: spi: Add driver for IMG SPFI controller Add support for the Synchronous Peripheral Flash Interface (SPFI) master controller found on IMG SoCs. The SPFI controller supports 5 chip-select lines and single/dual/quad mode SPI transfers. Signed-off-by: Andrew Bresticker Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi-img-spfi.c | 746 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 754 insertions(+) create mode 100644 drivers/spi/spi-img-spfi.c (limited to 'drivers/spi') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 84e7c9e6ccef..98de2074e8fe 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -225,6 +225,13 @@ config SPI_GPIO GPIO operations, you should be able to leverage that for better speed with a custom version of this driver; see the source code. +config SPI_IMG_SPFI + tristate "IMG SPFI controller" + depends on MIPS || COMPILE_TEST + help + This enables support for the SPFI master controller found on + IMG SoCs. + config SPI_IMX tristate "Freescale i.MX SPI controllers" depends on ARCH_MXC || COMPILE_TEST diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 78f24ca36fcf..37dc2e7d2c7e 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_SPI_FSL_LIB) += spi-fsl-lib.o obj-$(CONFIG_SPI_FSL_ESPI) += spi-fsl-espi.o obj-$(CONFIG_SPI_FSL_SPI) += spi-fsl-spi.o obj-$(CONFIG_SPI_GPIO) += spi-gpio.o +obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o obj-$(CONFIG_SPI_IMX) += spi-imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o diff --git a/drivers/spi/spi-img-spfi.c b/drivers/spi/spi-img-spfi.c new file mode 100644 index 000000000000..43781c9fe521 --- /dev/null +++ b/drivers/spi/spi-img-spfi.c @@ -0,0 +1,746 @@ +/* + * IMG SPFI controller driver + * + * Copyright (C) 2007,2008,2013 Imagination Technologies Ltd. + * Copyright (C) 2014 Google, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPFI_DEVICE_PARAMETER(x) (0x00 + 0x4 * (x)) +#define SPFI_DEVICE_PARAMETER_BITCLK_SHIFT 24 +#define SPFI_DEVICE_PARAMETER_BITCLK_MASK 0xff +#define SPFI_DEVICE_PARAMETER_CSSETUP_SHIFT 16 +#define SPFI_DEVICE_PARAMETER_CSSETUP_MASK 0xff +#define SPFI_DEVICE_PARAMETER_CSHOLD_SHIFT 8 +#define SPFI_DEVICE_PARAMETER_CSHOLD_MASK 0xff +#define SPFI_DEVICE_PARAMETER_CSDELAY_SHIFT 0 +#define SPFI_DEVICE_PARAMETER_CSDELAY_MASK 0xff + +#define SPFI_CONTROL 0x14 +#define SPFI_CONTROL_CONTINUE BIT(12) +#define SPFI_CONTROL_SOFT_RESET BIT(11) +#define SPFI_CONTROL_SEND_DMA BIT(10) +#define SPFI_CONTROL_GET_DMA BIT(9) +#define SPFI_CONTROL_TMODE_SHIFT 5 +#define SPFI_CONTROL_TMODE_MASK 0x7 +#define SPFI_CONTROL_TMODE_SINGLE 0 +#define SPFI_CONTROL_TMODE_DUAL 1 +#define SPFI_CONTROL_TMODE_QUAD 2 +#define SPFI_CONTROL_SPFI_EN BIT(0) + +#define SPFI_TRANSACTION 0x18 +#define SPFI_TRANSACTION_TSIZE_SHIFT 16 +#define SPFI_TRANSACTION_TSIZE_MASK 0xffff + +#define SPFI_PORT_STATE 0x1c +#define SPFI_PORT_STATE_DEV_SEL_SHIFT 20 +#define SPFI_PORT_STATE_DEV_SEL_MASK 0x7 +#define SPFI_PORT_STATE_CK_POL(x) BIT(19 - (x)) +#define SPFI_PORT_STATE_CK_PHASE(x) BIT(14 - (x)) + +#define SPFI_TX_32BIT_VALID_DATA 0x20 +#define SPFI_TX_8BIT_VALID_DATA 0x24 +#define SPFI_RX_32BIT_VALID_DATA 0x28 +#define SPFI_RX_8BIT_VALID_DATA 0x2c + +#define SPFI_INTERRUPT_STATUS 0x30 +#define SPFI_INTERRUPT_ENABLE 0x34 +#define SPFI_INTERRUPT_CLEAR 0x38 +#define SPFI_INTERRUPT_IACCESS BIT(12) +#define SPFI_INTERRUPT_GDEX8BIT BIT(11) +#define SPFI_INTERRUPT_ALLDONETRIG BIT(9) +#define SPFI_INTERRUPT_GDFUL BIT(8) +#define SPFI_INTERRUPT_GDHF BIT(7) +#define SPFI_INTERRUPT_GDEX32BIT BIT(6) +#define SPFI_INTERRUPT_GDTRIG BIT(5) +#define SPFI_INTERRUPT_SDFUL BIT(3) +#define SPFI_INTERRUPT_SDHF BIT(2) +#define SPFI_INTERRUPT_SDE BIT(1) +#define SPFI_INTERRUPT_SDTRIG BIT(0) + +/* + * There are four parallel FIFOs of 16 bytes each. The word buffer + * (*_32BIT_VALID_DATA) accesses all four FIFOs at once, resulting in an + * effective FIFO size of 64 bytes. The byte buffer (*_8BIT_VALID_DATA) + * accesses only a single FIFO, resulting in an effective FIFO size of + * 16 bytes. + */ +#define SPFI_32BIT_FIFO_SIZE 64 +#define SPFI_8BIT_FIFO_SIZE 16 + +struct img_spfi { + struct device *dev; + struct spi_master *master; + spinlock_t lock; + + void __iomem *regs; + phys_addr_t phys; + int irq; + struct clk *spfi_clk; + struct clk *sys_clk; + + struct dma_chan *rx_ch; + struct dma_chan *tx_ch; + bool tx_dma_busy; + bool rx_dma_busy; +}; + +static inline u32 spfi_readl(struct img_spfi *spfi, u32 reg) +{ + return readl(spfi->regs + reg); +} + +static inline void spfi_writel(struct img_spfi *spfi, u32 val, u32 reg) +{ + writel(val, spfi->regs + reg); +} + +static inline void spfi_start(struct img_spfi *spfi) +{ + u32 val; + + val = spfi_readl(spfi, SPFI_CONTROL); + val |= SPFI_CONTROL_SPFI_EN; + spfi_writel(spfi, val, SPFI_CONTROL); +} + +static inline void spfi_stop(struct img_spfi *spfi) +{ + u32 val; + + val = spfi_readl(spfi, SPFI_CONTROL); + val &= ~SPFI_CONTROL_SPFI_EN; + spfi_writel(spfi, val, SPFI_CONTROL); +} + +static inline void spfi_reset(struct img_spfi *spfi) +{ + spfi_writel(spfi, SPFI_CONTROL_SOFT_RESET, SPFI_CONTROL); + udelay(1); + spfi_writel(spfi, 0, SPFI_CONTROL); +} + +static void spfi_flush_tx_fifo(struct img_spfi *spfi) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(10); + + spfi_writel(spfi, SPFI_INTERRUPT_SDE, SPFI_INTERRUPT_CLEAR); + while (time_before(jiffies, timeout)) { + if (spfi_readl(spfi, SPFI_INTERRUPT_STATUS) & + SPFI_INTERRUPT_SDE) + return; + cpu_relax(); + } + + dev_err(spfi->dev, "Timed out waiting for FIFO to drain\n"); + spfi_reset(spfi); +} + +static unsigned int spfi_pio_write32(struct img_spfi *spfi, const u32 *buf, + unsigned int max) +{ + unsigned int count = 0; + u32 status; + + while (count < max) { + spfi_writel(spfi, SPFI_INTERRUPT_SDFUL, SPFI_INTERRUPT_CLEAR); + status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); + if (status & SPFI_INTERRUPT_SDFUL) + break; + spfi_writel(spfi, buf[count / 4], SPFI_TX_32BIT_VALID_DATA); + count += 4; + } + + return count; +} + +static unsigned int spfi_pio_write8(struct img_spfi *spfi, const u8 *buf, + unsigned int max) +{ + unsigned int count = 0; + u32 status; + + while (count < max) { + spfi_writel(spfi, SPFI_INTERRUPT_SDFUL, SPFI_INTERRUPT_CLEAR); + status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); + if (status & SPFI_INTERRUPT_SDFUL) + break; + spfi_writel(spfi, buf[count], SPFI_TX_8BIT_VALID_DATA); + count++; + } + + return count; +} + +static unsigned int spfi_pio_read32(struct img_spfi *spfi, u32 *buf, + unsigned int max) +{ + unsigned int count = 0; + u32 status; + + while (count < max) { + spfi_writel(spfi, SPFI_INTERRUPT_GDEX32BIT, + SPFI_INTERRUPT_CLEAR); + status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); + if (!(status & SPFI_INTERRUPT_GDEX32BIT)) + break; + buf[count / 4] = spfi_readl(spfi, SPFI_RX_32BIT_VALID_DATA); + count += 4; + } + + return count; +} + +static unsigned int spfi_pio_read8(struct img_spfi *spfi, u8 *buf, + unsigned int max) +{ + unsigned int count = 0; + u32 status; + + while (count < max) { + spfi_writel(spfi, SPFI_INTERRUPT_GDEX8BIT, + SPFI_INTERRUPT_CLEAR); + status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); + if (!(status & SPFI_INTERRUPT_GDEX8BIT)) + break; + buf[count] = spfi_readl(spfi, SPFI_RX_8BIT_VALID_DATA); + count++; + } + + return count; +} + +static int img_spfi_start_pio(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct img_spfi *spfi = spi_master_get_devdata(spi->master); + unsigned int tx_bytes = 0, rx_bytes = 0; + const void *tx_buf = xfer->tx_buf; + void *rx_buf = xfer->rx_buf; + unsigned long timeout; + + if (tx_buf) + tx_bytes = xfer->len; + if (rx_buf) + rx_bytes = xfer->len; + + spfi_start(spfi); + + timeout = jiffies + + msecs_to_jiffies(xfer->len * 8 * 1000 / xfer->speed_hz + 100); + while ((tx_bytes > 0 || rx_bytes > 0) && + time_before(jiffies, timeout)) { + unsigned int tx_count, rx_count; + + switch (xfer->bits_per_word) { + case 32: + tx_count = spfi_pio_write32(spfi, tx_buf, tx_bytes); + rx_count = spfi_pio_read32(spfi, rx_buf, rx_bytes); + break; + case 8: + default: + tx_count = spfi_pio_write8(spfi, tx_buf, tx_bytes); + rx_count = spfi_pio_read8(spfi, rx_buf, rx_bytes); + break; + } + + tx_buf += tx_count; + rx_buf += rx_count; + tx_bytes -= tx_count; + rx_bytes -= rx_count; + + cpu_relax(); + } + + if (rx_bytes > 0 || tx_bytes > 0) { + dev_err(spfi->dev, "PIO transfer timed out\n"); + spfi_reset(spfi); + return -ETIMEDOUT; + } + + if (tx_buf) + spfi_flush_tx_fifo(spfi); + spfi_stop(spfi); + + return 0; +} + +static void img_spfi_dma_rx_cb(void *data) +{ + struct img_spfi *spfi = data; + unsigned long flags; + + spin_lock_irqsave(&spfi->lock, flags); + + spfi->rx_dma_busy = false; + if (!spfi->tx_dma_busy) { + spfi_stop(spfi); + spi_finalize_current_transfer(spfi->master); + } + + spin_unlock_irqrestore(&spfi->lock, flags); +} + +static void img_spfi_dma_tx_cb(void *data) +{ + struct img_spfi *spfi = data; + unsigned long flags; + + spfi_flush_tx_fifo(spfi); + + spin_lock_irqsave(&spfi->lock, flags); + + spfi->tx_dma_busy = false; + if (!spfi->rx_dma_busy) { + spfi_stop(spfi); + spi_finalize_current_transfer(spfi->master); + } + + spin_unlock_irqrestore(&spfi->lock, flags); +} + +static int img_spfi_start_dma(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct img_spfi *spfi = spi_master_get_devdata(spi->master); + struct dma_async_tx_descriptor *rxdesc = NULL, *txdesc = NULL; + struct dma_slave_config rxconf, txconf; + + spfi->rx_dma_busy = false; + spfi->tx_dma_busy = false; + + if (xfer->rx_buf) { + rxconf.direction = DMA_DEV_TO_MEM; + switch (xfer->bits_per_word) { + case 32: + rxconf.src_addr = spfi->phys + SPFI_RX_32BIT_VALID_DATA; + rxconf.src_addr_width = 4; + rxconf.src_maxburst = 4; + break; + case 8: + default: + rxconf.src_addr = spfi->phys + SPFI_RX_8BIT_VALID_DATA; + rxconf.src_addr_width = 1; + rxconf.src_maxburst = 1; + } + dmaengine_slave_config(spfi->rx_ch, &rxconf); + + rxdesc = dmaengine_prep_slave_sg(spfi->rx_ch, xfer->rx_sg.sgl, + xfer->rx_sg.nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!rxdesc) + goto stop_dma; + + rxdesc->callback = img_spfi_dma_rx_cb; + rxdesc->callback_param = spfi; + } + + if (xfer->tx_buf) { + txconf.direction = DMA_MEM_TO_DEV; + switch (xfer->bits_per_word) { + case 32: + txconf.dst_addr = spfi->phys + SPFI_TX_32BIT_VALID_DATA; + txconf.dst_addr_width = 4; + txconf.dst_maxburst = 4; + break; + case 8: + default: + txconf.dst_addr = spfi->phys + SPFI_TX_8BIT_VALID_DATA; + txconf.dst_addr_width = 1; + txconf.dst_maxburst = 1; + break; + } + dmaengine_slave_config(spfi->tx_ch, &txconf); + + txdesc = dmaengine_prep_slave_sg(spfi->tx_ch, xfer->tx_sg.sgl, + xfer->tx_sg.nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT); + if (!txdesc) + goto stop_dma; + + txdesc->callback = img_spfi_dma_tx_cb; + txdesc->callback_param = spfi; + } + + if (xfer->rx_buf) { + spfi->rx_dma_busy = true; + dmaengine_submit(rxdesc); + dma_async_issue_pending(spfi->rx_ch); + } + + if (xfer->tx_buf) { + spfi->tx_dma_busy = true; + dmaengine_submit(txdesc); + dma_async_issue_pending(spfi->tx_ch); + } + + spfi_start(spfi); + + return 1; + +stop_dma: + dmaengine_terminate_all(spfi->rx_ch); + dmaengine_terminate_all(spfi->tx_ch); + return -EIO; +} + +static void img_spfi_config(struct spi_master *master, struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct img_spfi *spfi = spi_master_get_devdata(spi->master); + u32 val, div; + + /* + * output = spfi_clk * (BITCLK / 512), where BITCLK must be a + * power of 2 up to 256 (where 255 == 256 since BITCLK is 8 bits) + */ + div = DIV_ROUND_UP(master->max_speed_hz, xfer->speed_hz); + div = clamp(512 / (1 << get_count_order(div)), 1, 255); + + val = spfi_readl(spfi, SPFI_DEVICE_PARAMETER(spi->chip_select)); + val &= ~(SPFI_DEVICE_PARAMETER_BITCLK_MASK << + SPFI_DEVICE_PARAMETER_BITCLK_SHIFT); + val |= div << SPFI_DEVICE_PARAMETER_BITCLK_SHIFT; + spfi_writel(spfi, val, SPFI_DEVICE_PARAMETER(spi->chip_select)); + + val = spfi_readl(spfi, SPFI_CONTROL); + val &= ~(SPFI_CONTROL_SEND_DMA | SPFI_CONTROL_GET_DMA); + if (xfer->tx_buf) + val |= SPFI_CONTROL_SEND_DMA; + if (xfer->rx_buf) + val |= SPFI_CONTROL_GET_DMA; + val &= ~(SPFI_CONTROL_TMODE_MASK << SPFI_CONTROL_TMODE_SHIFT); + if (xfer->tx_nbits == SPI_NBITS_DUAL && + xfer->rx_nbits == SPI_NBITS_DUAL) + val |= SPFI_CONTROL_TMODE_DUAL << SPFI_CONTROL_TMODE_SHIFT; + else if (xfer->tx_nbits == SPI_NBITS_QUAD && + xfer->rx_nbits == SPI_NBITS_QUAD) + val |= SPFI_CONTROL_TMODE_QUAD << SPFI_CONTROL_TMODE_SHIFT; + val &= ~SPFI_CONTROL_CONTINUE; + if (!xfer->cs_change && !list_is_last(&xfer->transfer_list, + &master->cur_msg->transfers)) + val |= SPFI_CONTROL_CONTINUE; + spfi_writel(spfi, val, SPFI_CONTROL); + + val = spfi_readl(spfi, SPFI_PORT_STATE); + if (spi->mode & SPI_CPHA) + val |= SPFI_PORT_STATE_CK_PHASE(spi->chip_select); + else + val &= ~SPFI_PORT_STATE_CK_PHASE(spi->chip_select); + if (spi->mode & SPI_CPOL) + val |= SPFI_PORT_STATE_CK_POL(spi->chip_select); + else + val &= ~SPFI_PORT_STATE_CK_POL(spi->chip_select); + spfi_writel(spfi, val, SPFI_PORT_STATE); + + spfi_writel(spfi, xfer->len << SPFI_TRANSACTION_TSIZE_SHIFT, + SPFI_TRANSACTION); +} + +static int img_spfi_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct img_spfi *spfi = spi_master_get_devdata(spi->master); + bool dma_reset = false; + unsigned long flags; + int ret; + + /* + * Stop all DMA and reset the controller if the previous transaction + * timed-out and never completed it's DMA. + */ + spin_lock_irqsave(&spfi->lock, flags); + if (spfi->tx_dma_busy || spfi->rx_dma_busy) { + dev_err(spfi->dev, "SPI DMA still busy\n"); + dma_reset = true; + } + spin_unlock_irqrestore(&spfi->lock, flags); + + if (dma_reset) { + dmaengine_terminate_all(spfi->tx_ch); + dmaengine_terminate_all(spfi->rx_ch); + spfi_reset(spfi); + } + + img_spfi_config(master, spi, xfer); + if (master->can_dma && master->can_dma(master, spi, xfer)) + ret = img_spfi_start_dma(master, spi, xfer); + else + ret = img_spfi_start_pio(master, spi, xfer); + + return ret; +} + +static void img_spfi_set_cs(struct spi_device *spi, bool enable) +{ + struct img_spfi *spfi = spi_master_get_devdata(spi->master); + u32 val; + + val = spfi_readl(spfi, SPFI_PORT_STATE); + val &= ~(SPFI_PORT_STATE_DEV_SEL_MASK << SPFI_PORT_STATE_DEV_SEL_SHIFT); + val |= spi->chip_select << SPFI_PORT_STATE_DEV_SEL_SHIFT; + spfi_writel(spfi, val, SPFI_PORT_STATE); +} + +static bool img_spfi_can_dma(struct spi_master *master, struct spi_device *spi, + struct spi_transfer *xfer) +{ + if (xfer->bits_per_word == 8 && xfer->len > SPFI_8BIT_FIFO_SIZE) + return true; + if (xfer->bits_per_word == 32 && xfer->len > SPFI_32BIT_FIFO_SIZE) + return true; + return false; +} + +static irqreturn_t img_spfi_irq(int irq, void *dev_id) +{ + struct img_spfi *spfi = (struct img_spfi *)dev_id; + u32 status; + + status = spfi_readl(spfi, SPFI_INTERRUPT_STATUS); + if (status & SPFI_INTERRUPT_IACCESS) { + spfi_writel(spfi, SPFI_INTERRUPT_IACCESS, SPFI_INTERRUPT_CLEAR); + dev_err(spfi->dev, "Illegal access interrupt"); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int img_spfi_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct img_spfi *spfi; + struct resource *res; + int ret; + + master = spi_alloc_master(&pdev->dev, sizeof(*spfi)); + if (!master) + return -ENOMEM; + platform_set_drvdata(pdev, master); + + spfi = spi_master_get_devdata(master); + spfi->dev = &pdev->dev; + spfi->master = master; + spin_lock_init(&spfi->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + spfi->regs = devm_ioremap_resource(spfi->dev, res); + if (IS_ERR(spfi->regs)) { + ret = PTR_ERR(spfi->regs); + goto put_spi; + } + spfi->phys = res->start; + + spfi->irq = platform_get_irq(pdev, 0); + if (spfi->irq < 0) { + ret = spfi->irq; + goto put_spi; + } + ret = devm_request_irq(spfi->dev, spfi->irq, img_spfi_irq, + IRQ_TYPE_LEVEL_HIGH, dev_name(spfi->dev), spfi); + if (ret) + goto put_spi; + + spfi->sys_clk = devm_clk_get(spfi->dev, "sys"); + if (IS_ERR(spfi->sys_clk)) { + ret = PTR_ERR(spfi->sys_clk); + goto put_spi; + } + spfi->spfi_clk = devm_clk_get(spfi->dev, "spfi"); + if (IS_ERR(spfi->spfi_clk)) { + ret = PTR_ERR(spfi->spfi_clk); + goto put_spi; + } + + ret = clk_prepare_enable(spfi->sys_clk); + if (ret) + goto put_spi; + ret = clk_prepare_enable(spfi->spfi_clk); + if (ret) + goto disable_pclk; + + spfi_reset(spfi); + /* + * Only enable the error (IACCESS) interrupt. In PIO mode we'll + * poll the status of the FIFOs. + */ + spfi_writel(spfi, SPFI_INTERRUPT_IACCESS, SPFI_INTERRUPT_ENABLE); + + master->auto_runtime_pm = true; + master->bus_num = pdev->id; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_DUAL | SPI_RX_DUAL; + if (of_property_read_bool(spfi->dev->of_node, "img,supports-quad-mode")) + master->mode_bits |= SPI_TX_QUAD | SPI_RX_QUAD; + master->num_chipselect = 5; + master->dev.of_node = pdev->dev.of_node; + master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(8); + master->max_speed_hz = clk_get_rate(spfi->spfi_clk); + master->min_speed_hz = master->max_speed_hz / 512; + + master->set_cs = img_spfi_set_cs; + master->transfer_one = img_spfi_transfer_one; + + spfi->tx_ch = dma_request_slave_channel(spfi->dev, "tx"); + spfi->rx_ch = dma_request_slave_channel(spfi->dev, "rx"); + if (!spfi->tx_ch || !spfi->rx_ch) { + if (spfi->tx_ch) + dma_release_channel(spfi->tx_ch); + if (spfi->rx_ch) + dma_release_channel(spfi->rx_ch); + dev_warn(spfi->dev, "Failed to get DMA channels, falling back to PIO mode\n"); + } else { + master->dma_tx = spfi->tx_ch; + master->dma_rx = spfi->rx_ch; + master->can_dma = img_spfi_can_dma; + } + + pm_runtime_set_active(spfi->dev); + pm_runtime_enable(spfi->dev); + + ret = devm_spi_register_master(spfi->dev, master); + if (ret) + goto disable_pm; + + return 0; + +disable_pm: + pm_runtime_disable(spfi->dev); + if (spfi->rx_ch) + dma_release_channel(spfi->rx_ch); + if (spfi->tx_ch) + dma_release_channel(spfi->tx_ch); + clk_disable_unprepare(spfi->spfi_clk); +disable_pclk: + clk_disable_unprepare(spfi->sys_clk); +put_spi: + spi_master_put(master); + + return ret; +} + +static int img_spfi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct img_spfi *spfi = spi_master_get_devdata(master); + + if (spfi->tx_ch) + dma_release_channel(spfi->tx_ch); + if (spfi->rx_ch) + dma_release_channel(spfi->rx_ch); + + pm_runtime_disable(spfi->dev); + if (!pm_runtime_status_suspended(spfi->dev)) { + clk_disable_unprepare(spfi->spfi_clk); + clk_disable_unprepare(spfi->sys_clk); + } + + spi_master_put(master); + + return 0; +} + +#ifdef CONFIG_PM_RUNTIME +static int img_spfi_runtime_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct img_spfi *spfi = spi_master_get_devdata(master); + + clk_disable_unprepare(spfi->spfi_clk); + clk_disable_unprepare(spfi->sys_clk); + + return 0; +} + +static int img_spfi_runtime_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct img_spfi *spfi = spi_master_get_devdata(master); + int ret; + + ret = clk_prepare_enable(spfi->sys_clk); + if (ret) + return ret; + ret = clk_prepare_enable(spfi->spfi_clk); + if (ret) { + clk_disable_unprepare(spfi->sys_clk); + return ret; + } + + return 0; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM_SLEEP +static int img_spfi_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + + return spi_master_suspend(master); +} + +static int img_spfi_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct img_spfi *spfi = spi_master_get_devdata(master); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret) + return ret; + spfi_reset(spfi); + pm_runtime_put(dev); + + return spi_master_resume(master); +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops img_spfi_pm_ops = { + SET_RUNTIME_PM_OPS(img_spfi_runtime_suspend, img_spfi_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(img_spfi_suspend, img_spfi_resume) +}; + +static const struct of_device_id img_spfi_of_match[] = { + { .compatible = "img,spfi", }, + { }, +}; +MODULE_DEVICE_TABLE(of, img_spfi_of_match); + +static struct platform_driver img_spfi_driver = { + .driver = { + .name = "img-spfi", + .pm = &img_spfi_pm_ops, + .of_match_table = of_match_ptr(img_spfi_of_match), + }, + .probe = img_spfi_probe, + .remove = img_spfi_remove, +}; +module_platform_driver(img_spfi_driver); + +MODULE_DESCRIPTION("IMG SPFI controller driver"); +MODULE_AUTHOR("Andrew Bresticker "); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.2 From c3e4bc5434d2999ec3b00eadd1945eadf13fd0de Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Sat, 22 Nov 2014 16:21:41 +0100 Subject: spi: meson: Add support for Amlogic Meson SPIFC This is a driver for the Amlogic Meson SPIFC (SPI flash controller), which is one of the two SPI controllers available on the SoC. It doesn't support DMA and has a 64-byte unified transmit/receive buffer. The device is optimized for interfacing with SPI NOR memories and allows the execution of standard operations such as read, page program, sector erase, etc. in a simplified way, toggling a bit in a dedicated register. The driver doesn't use those predefined commands and relies only on custom transfers. Signed-off-by: Beniamino Galvani Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 7 + drivers/spi/Makefile | 1 + drivers/spi/spi-meson-spifc.c | 462 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 470 insertions(+) create mode 100644 drivers/spi/spi-meson-spifc.c (limited to 'drivers/spi') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 84e7c9e6ccef..70f20b99e5ff 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -301,6 +301,13 @@ config SPI_FSL_ESPI From MPC8536, 85xx platform uses the controller, and all P10xx, P20xx, P30xx,P40xx, P50xx uses this controller. +config SPI_MESON_SPIFC + tristate "Amlogic Meson SPIFC controller" + depends on ARCH_MESON || COMPILE_TEST + help + This enables master mode support for the SPIFC (SPI flash + controller) available in Amlogic Meson SoCs. + config SPI_OC_TINY tristate "OpenCores tiny SPI" depends on GPIOLIB diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 78f24ca36fcf..9b8a7472b5b8 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_SPI_FSL_SPI) += spi-fsl-spi.o obj-$(CONFIG_SPI_GPIO) += spi-gpio.o obj-$(CONFIG_SPI_IMX) += spi-imx.o obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o +obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o obj-$(CONFIG_SPI_MPC52xx) += spi-mpc52xx.o diff --git a/drivers/spi/spi-meson-spifc.c b/drivers/spi/spi-meson-spifc.c new file mode 100644 index 000000000000..f536476710ad --- /dev/null +++ b/drivers/spi/spi-meson-spifc.c @@ -0,0 +1,462 @@ +/* + * Driver for Amlogic Meson SPI flash controller (SPIFC) + * + * Copyright (C) 2014 Beniamino Galvani + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* register map */ +#define REG_CMD 0x00 +#define REG_ADDR 0x04 +#define REG_CTRL 0x08 +#define REG_CTRL1 0x0c +#define REG_STATUS 0x10 +#define REG_CTRL2 0x14 +#define REG_CLOCK 0x18 +#define REG_USER 0x1c +#define REG_USER1 0x20 +#define REG_USER2 0x24 +#define REG_USER3 0x28 +#define REG_USER4 0x2c +#define REG_SLAVE 0x30 +#define REG_SLAVE1 0x34 +#define REG_SLAVE2 0x38 +#define REG_SLAVE3 0x3c +#define REG_C0 0x40 +#define REG_B8 0x60 +#define REG_MAX 0x7c + +/* register fields */ +#define CMD_USER BIT(18) +#define CTRL_ENABLE_AHB BIT(17) +#define CLOCK_SOURCE BIT(31) +#define CLOCK_DIV_SHIFT 12 +#define CLOCK_DIV_MASK (0x3f << CLOCK_DIV_SHIFT) +#define CLOCK_CNT_HIGH_SHIFT 6 +#define CLOCK_CNT_HIGH_MASK (0x3f << CLOCK_CNT_HIGH_SHIFT) +#define CLOCK_CNT_LOW_SHIFT 0 +#define CLOCK_CNT_LOW_MASK (0x3f << CLOCK_CNT_LOW_SHIFT) +#define USER_DIN_EN_MS BIT(0) +#define USER_CMP_MODE BIT(2) +#define USER_UC_DOUT_SEL BIT(27) +#define USER_UC_DIN_SEL BIT(28) +#define USER_UC_MASK ((BIT(5) - 1) << 27) +#define USER1_BN_UC_DOUT_SHIFT 17 +#define USER1_BN_UC_DOUT_MASK (0xff << 16) +#define USER1_BN_UC_DIN_SHIFT 8 +#define USER1_BN_UC_DIN_MASK (0xff << 8) +#define USER4_CS_ACT BIT(30) +#define SLAVE_TRST_DONE BIT(4) +#define SLAVE_OP_MODE BIT(30) +#define SLAVE_SW_RST BIT(31) + +#define SPIFC_BUFFER_SIZE 64 + +/** + * struct meson_spifc + * @master: the SPI master + * @regmap: regmap for device registers + * @clk: input clock of the built-in baud rate generator + * @device: the device structure + */ +struct meson_spifc { + struct spi_master *master; + struct regmap *regmap; + struct clk *clk; + struct device *dev; +}; + +static struct regmap_config spifc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = REG_MAX, +}; + +/** + * meson_spifc_wait_ready() - wait for the current operation to terminate + * @spifc: the Meson SPI device + * Return: 0 on success, a negative value on error + */ +static int meson_spifc_wait_ready(struct meson_spifc *spifc) +{ + unsigned long deadline = jiffies + msecs_to_jiffies(5); + u32 data; + + do { + regmap_read(spifc->regmap, REG_SLAVE, &data); + if (data & SLAVE_TRST_DONE) + return 0; + cond_resched(); + } while (!time_after(jiffies, deadline)); + + return -ETIMEDOUT; +} + +/** + * meson_spifc_drain_buffer() - copy data from device buffer to memory + * @spifc: the Meson SPI device + * @buf: the destination buffer + * @len: number of bytes to copy + */ +static void meson_spifc_drain_buffer(struct meson_spifc *spifc, u8 *buf, + int len) +{ + u32 data; + int i = 0; + + while (i < len) { + regmap_read(spifc->regmap, REG_C0 + i, &data); + + if (len - i >= 4) { + *((u32 *)buf) = data; + buf += 4; + } else { + memcpy(buf, &data, len - i); + break; + } + i += 4; + } +} + +/** + * meson_spifc_fill_buffer() - copy data from memory to device buffer + * @spifc: the Meson SPI device + * @buf: the source buffer + * @len: number of bytes to copy + */ +static void meson_spifc_fill_buffer(struct meson_spifc *spifc, const u8 *buf, + int len) +{ + u32 data; + int i = 0; + + while (i < len) { + if (len - i >= 4) + data = *(u32 *)buf; + else + memcpy(&data, buf, len - i); + + regmap_write(spifc->regmap, REG_C0 + i, data); + + buf += 4; + i += 4; + } +} + +/** + * meson_spifc_setup_speed() - program the clock divider + * @spifc: the Meson SPI device + * @speed: desired speed in Hz + */ +void meson_spifc_setup_speed(struct meson_spifc *spifc, u32 speed) +{ + unsigned long parent, value; + int n; + + parent = clk_get_rate(spifc->clk); + n = max_t(int, parent / speed - 1, 1); + + dev_dbg(spifc->dev, "parent %lu, speed %u, n %d\n", parent, + speed, n); + + value = (n << CLOCK_DIV_SHIFT) & CLOCK_DIV_MASK; + value |= (n << CLOCK_CNT_LOW_SHIFT) & CLOCK_CNT_LOW_MASK; + value |= (((n + 1) / 2 - 1) << CLOCK_CNT_HIGH_SHIFT) & + CLOCK_CNT_HIGH_MASK; + + regmap_write(spifc->regmap, REG_CLOCK, value); +} + +/** + * meson_spifc_txrx() - transfer a chunk of data + * @spifc: the Meson SPI device + * @xfer: the current SPI transfer + * @offset: offset of the data to transfer + * @len: length of the data to transfer + * @last_xfer: whether this is the last transfer of the message + * @last_chunk: whether this is the last chunk of the transfer + * Return: 0 on success, a negative value on error + */ +static int meson_spifc_txrx(struct meson_spifc *spifc, + struct spi_transfer *xfer, + int offset, int len, bool last_xfer, + bool last_chunk) +{ + bool keep_cs = true; + int ret; + + if (xfer->tx_buf) + meson_spifc_fill_buffer(spifc, xfer->tx_buf + offset, len); + + /* enable DOUT stage */ + regmap_update_bits(spifc->regmap, REG_USER, USER_UC_MASK, + USER_UC_DOUT_SEL); + regmap_write(spifc->regmap, REG_USER1, + (8 * len - 1) << USER1_BN_UC_DOUT_SHIFT); + + /* enable data input during DOUT */ + regmap_update_bits(spifc->regmap, REG_USER, USER_DIN_EN_MS, + USER_DIN_EN_MS); + + if (last_chunk) { + if (last_xfer) + keep_cs = xfer->cs_change; + else + keep_cs = !xfer->cs_change; + } + + regmap_update_bits(spifc->regmap, REG_USER4, USER4_CS_ACT, + keep_cs ? USER4_CS_ACT : 0); + + /* clear transition done bit */ + regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_TRST_DONE, 0); + /* start transfer */ + regmap_update_bits(spifc->regmap, REG_CMD, CMD_USER, CMD_USER); + + ret = meson_spifc_wait_ready(spifc); + + if (!ret && xfer->rx_buf) + meson_spifc_drain_buffer(spifc, xfer->rx_buf + offset, len); + + return ret; +} + +/** + * meson_spifc_transfer_one() - perform a single transfer + * @master: the SPI master + * @spi: the SPI device + * @xfer: the current SPI transfer + * Return: 0 on success, a negative value on error + */ +static int meson_spifc_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct meson_spifc *spifc = spi_master_get_devdata(master); + int len, done = 0, ret = 0; + + meson_spifc_setup_speed(spifc, xfer->speed_hz); + + regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, 0); + + while (done < xfer->len && !ret) { + len = min_t(int, xfer->len - done, SPIFC_BUFFER_SIZE); + ret = meson_spifc_txrx(spifc, xfer, done, len, + spi_transfer_is_last(master, xfer), + done + len >= xfer->len); + done += len; + } + + regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, + CTRL_ENABLE_AHB); + + return ret; +} + +/** + * meson_spifc_hw_init() - reset and initialize the SPI controller + * @spifc: the Meson SPI device + */ +static void meson_spifc_hw_init(struct meson_spifc *spifc) +{ + /* reset device */ + regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_SW_RST, + SLAVE_SW_RST); + /* disable compatible mode */ + regmap_update_bits(spifc->regmap, REG_USER, USER_CMP_MODE, 0); + /* set master mode */ + regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_OP_MODE, 0); +} + +static int meson_spifc_probe(struct platform_device *pdev) +{ + struct spi_master *master; + struct meson_spifc *spifc; + struct resource *res; + void __iomem *base; + unsigned int rate; + int ret = 0; + + master = spi_alloc_master(&pdev->dev, sizeof(struct meson_spifc)); + if (!master) + return -ENOMEM; + + platform_set_drvdata(pdev, master); + + spifc = spi_master_get_devdata(master); + spifc->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(spifc->dev, res); + if (IS_ERR(base)) { + ret = PTR_ERR(base); + goto out_err; + } + + spifc->regmap = devm_regmap_init_mmio(spifc->dev, base, + &spifc_regmap_config); + if (IS_ERR(spifc->regmap)) { + ret = PTR_ERR(spifc->regmap); + goto out_err; + } + + spifc->clk = devm_clk_get(spifc->dev, NULL); + if (IS_ERR(spifc->clk)) { + dev_err(spifc->dev, "missing clock\n"); + ret = PTR_ERR(spifc->clk); + goto out_err; + } + + ret = clk_prepare_enable(spifc->clk); + if (ret) { + dev_err(spifc->dev, "can't prepare clock\n"); + goto out_err; + } + + rate = clk_get_rate(spifc->clk); + + master->num_chipselect = 1; + master->dev.of_node = pdev->dev.of_node; + master->bits_per_word_mask = SPI_BPW_MASK(8); + master->auto_runtime_pm = true; + master->transfer_one = meson_spifc_transfer_one; + master->min_speed_hz = rate >> 6; + master->max_speed_hz = rate >> 1; + + meson_spifc_hw_init(spifc); + + pm_runtime_set_active(spifc->dev); + pm_runtime_enable(spifc->dev); + + ret = devm_spi_register_master(spifc->dev, master); + if (ret) { + dev_err(spifc->dev, "failed to register spi master\n"); + goto out_clk; + } + + return 0; +out_clk: + clk_disable_unprepare(spifc->clk); +out_err: + spi_master_put(master); + return ret; +} + +static int meson_spifc_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct meson_spifc *spifc = spi_master_get_devdata(master); + + pm_runtime_get_sync(&pdev->dev); + clk_disable_unprepare(spifc->clk); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int meson_spifc_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct meson_spifc *spifc = spi_master_get_devdata(master); + int ret; + + ret = spi_master_suspend(master); + if (ret) + return ret; + + if (!pm_runtime_suspended(dev)) + clk_disable_unprepare(spifc->clk); + + return 0; +} + +static int meson_spifc_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct meson_spifc *spifc = spi_master_get_devdata(master); + int ret; + + if (!pm_runtime_suspended(dev)) { + ret = clk_prepare_enable(spifc->clk); + if (ret) + return ret; + } + + meson_spifc_hw_init(spifc); + + ret = spi_master_resume(master); + if (ret) + clk_disable_unprepare(spifc->clk); + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int meson_spifc_runtime_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct meson_spifc *spifc = spi_master_get_devdata(master); + + clk_disable_unprepare(spifc->clk); + + return 0; +} + +static int meson_spifc_runtime_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct meson_spifc *spifc = spi_master_get_devdata(master); + + return clk_prepare_enable(spifc->clk); +} +#endif /* CONFIG_PM_RUNTIME */ + +static const struct dev_pm_ops meson_spifc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(meson_spifc_suspend, meson_spifc_resume) + SET_RUNTIME_PM_OPS(meson_spifc_runtime_suspend, + meson_spifc_runtime_resume, + NULL) +}; + +static const struct of_device_id meson_spifc_dt_match[] = { + { .compatible = "amlogic,meson6-spifc", }, + { }, +}; + +static struct platform_driver meson_spifc_driver = { + .probe = meson_spifc_probe, + .remove = meson_spifc_remove, + .driver = { + .name = "meson-spifc", + .of_match_table = of_match_ptr(meson_spifc_dt_match), + .pm = &meson_spifc_pm_ops, + }, +}; + +module_platform_driver(meson_spifc_driver); + +MODULE_AUTHOR("Beniamino Galvani "); +MODULE_DESCRIPTION("Amlogic Meson SPIFC driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.2 From 008c2a2ef0af9a31a848948034d7d59b37f9a9e1 Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Tue, 25 Nov 2014 08:36:12 +0800 Subject: spi: meson: meson_spifc_setup_speed() can be static drivers/spi/spi-meson-spifc.c:171:6: sparse: symbol 'meson_spifc_setup_speed' was not declared. Should it be static? Signed-off-by: Fengguang Wu Signed-off-by: Mark Brown --- drivers/spi/spi-meson-spifc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-meson-spifc.c b/drivers/spi/spi-meson-spifc.c index f536476710ad..0e48f8c2037d 100644 --- a/drivers/spi/spi-meson-spifc.c +++ b/drivers/spi/spi-meson-spifc.c @@ -168,7 +168,7 @@ static void meson_spifc_fill_buffer(struct meson_spifc *spifc, const u8 *buf, * @spifc: the Meson SPI device * @speed: desired speed in Hz */ -void meson_spifc_setup_speed(struct meson_spifc *spifc, u32 speed) +static void meson_spifc_setup_speed(struct meson_spifc *spifc, u32 speed) { unsigned long parent, value; int n; -- cgit v1.2.2 From 1327ecd47fe4e36bae6bcda55b3282c08193286a Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 27 Nov 2014 00:07:48 +0100 Subject: spi: meson: Select REGMAP_MMIO The Meson SPIFC driver uses regmap mmio functions and so it must select REGMAP_MMIO to avoid the following build error: spi-meson-spifc.c: undefined reference to `devm_regmap_init_mmio_clk' Reported-by: Jim Davis Signed-off-by: Beniamino Galvani Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/spi') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 70f20b99e5ff..9d277955b4de 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -304,6 +304,7 @@ config SPI_FSL_ESPI config SPI_MESON_SPIFC tristate "Amlogic Meson SPIFC controller" depends on ARCH_MESON || COMPILE_TEST + select REGMAP_MMIO help This enables master mode support for the SPIFC (SPI flash controller) available in Amlogic Meson SoCs. -- cgit v1.2.2 From c592becbe704127ef0b89233ff7f285637491154 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 3 Dec 2014 07:56:17 +0100 Subject: spi: fsl-(e)spi: migrate to generic master queueing Migrates the fsl-(e)spi driver to use the generic master queuing. Avoids the "master is unqueued, this is deprecated" warning. Signed-off-by: Heiner Kallweit Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-espi.c | 13 +++++----- drivers/spi/spi-fsl-lib.c | 59 +--------------------------------------------- drivers/spi/spi-fsl-lib.h | 10 +------- drivers/spi/spi-fsl-spi.c | 14 +++++------ 4 files changed, 14 insertions(+), 82 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-fsl-espi.c b/drivers/spi/spi-fsl-espi.c index a7f94b6a9e70..90be6405e789 100644 --- a/drivers/spi/spi-fsl-espi.c +++ b/drivers/spi/spi-fsl-espi.c @@ -411,7 +411,8 @@ static void fsl_espi_rw_trans(struct spi_message *m, kfree(local_buf); } -static void fsl_espi_do_one_msg(struct spi_message *m) +static int fsl_espi_do_one_msg(struct spi_master *master, + struct spi_message *m) { struct spi_transfer *t; u8 *rx_buf = NULL; @@ -441,8 +442,8 @@ static void fsl_espi_do_one_msg(struct spi_message *m) m->actual_length = espi_trans.actual_length; m->status = espi_trans.status; - if (m->complete) - m->complete(m->context); + spi_finalize_current_message(master); + return 0; } static int fsl_espi_setup(struct spi_device *spi) @@ -607,16 +608,14 @@ static struct spi_master * fsl_espi_probe(struct device *dev, dev_set_drvdata(dev, master); - ret = mpc8xxx_spi_probe(dev, mem, irq); - if (ret) - goto err_probe; + mpc8xxx_spi_probe(dev, mem, irq); master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16); master->setup = fsl_espi_setup; master->cleanup = fsl_espi_cleanup; + master->transfer_one_message = fsl_espi_do_one_msg; mpc8xxx_spi = spi_master_get_devdata(master); - mpc8xxx_spi->spi_do_one_msg = fsl_espi_do_one_msg; mpc8xxx_spi->spi_remove = fsl_espi_remove; mpc8xxx_spi->reg_base = ioremap(mem->start, resource_size(mem)); diff --git a/drivers/spi/spi-fsl-lib.c b/drivers/spi/spi-fsl-lib.c index 5ddb5b098e4e..446b737e1532 100644 --- a/drivers/spi/spi-fsl-lib.c +++ b/drivers/spi/spi-fsl-lib.c @@ -61,44 +61,6 @@ struct mpc8xxx_spi_probe_info *to_of_pinfo(struct fsl_spi_platform_data *pdata) return container_of(pdata, struct mpc8xxx_spi_probe_info, pdata); } -static void mpc8xxx_spi_work(struct work_struct *work) -{ - struct mpc8xxx_spi *mpc8xxx_spi = container_of(work, struct mpc8xxx_spi, - work); - - spin_lock_irq(&mpc8xxx_spi->lock); - while (!list_empty(&mpc8xxx_spi->queue)) { - struct spi_message *m = container_of(mpc8xxx_spi->queue.next, - struct spi_message, queue); - - list_del_init(&m->queue); - spin_unlock_irq(&mpc8xxx_spi->lock); - - if (mpc8xxx_spi->spi_do_one_msg) - mpc8xxx_spi->spi_do_one_msg(m); - - spin_lock_irq(&mpc8xxx_spi->lock); - } - spin_unlock_irq(&mpc8xxx_spi->lock); -} - -int mpc8xxx_spi_transfer(struct spi_device *spi, - struct spi_message *m) -{ - struct mpc8xxx_spi *mpc8xxx_spi = spi_master_get_devdata(spi->master); - unsigned long flags; - - m->actual_length = 0; - m->status = -EINPROGRESS; - - spin_lock_irqsave(&mpc8xxx_spi->lock, flags); - list_add_tail(&m->queue, &mpc8xxx_spi->queue); - queue_work(mpc8xxx_spi->workqueue, &mpc8xxx_spi->work); - spin_unlock_irqrestore(&mpc8xxx_spi->lock, flags); - - return 0; -} - const char *mpc8xxx_spi_strmode(unsigned int flags) { if (flags & SPI_QE_CPU_MODE) { @@ -114,13 +76,12 @@ const char *mpc8xxx_spi_strmode(unsigned int flags) return "CPU"; } -int mpc8xxx_spi_probe(struct device *dev, struct resource *mem, +void mpc8xxx_spi_probe(struct device *dev, struct resource *mem, unsigned int irq) { struct fsl_spi_platform_data *pdata = dev_get_platdata(dev); struct spi_master *master; struct mpc8xxx_spi *mpc8xxx_spi; - int ret = 0; master = dev_get_drvdata(dev); @@ -128,7 +89,6 @@ int mpc8xxx_spi_probe(struct device *dev, struct resource *mem, master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_LOOP; - master->transfer = mpc8xxx_spi_transfer; master->dev.of_node = dev->of_node; mpc8xxx_spi = spi_master_get_devdata(master); @@ -147,22 +107,7 @@ int mpc8xxx_spi_probe(struct device *dev, struct resource *mem, master->bus_num = pdata->bus_num; master->num_chipselect = pdata->max_chipselect; - spin_lock_init(&mpc8xxx_spi->lock); init_completion(&mpc8xxx_spi->done); - INIT_WORK(&mpc8xxx_spi->work, mpc8xxx_spi_work); - INIT_LIST_HEAD(&mpc8xxx_spi->queue); - - mpc8xxx_spi->workqueue = create_singlethread_workqueue( - dev_name(master->dev.parent)); - if (mpc8xxx_spi->workqueue == NULL) { - ret = -EBUSY; - goto err; - } - - return 0; - -err: - return ret; } int mpc8xxx_spi_remove(struct device *dev) @@ -173,8 +118,6 @@ int mpc8xxx_spi_remove(struct device *dev) master = dev_get_drvdata(dev); mpc8xxx_spi = spi_master_get_devdata(master); - flush_workqueue(mpc8xxx_spi->workqueue); - destroy_workqueue(mpc8xxx_spi->workqueue); spi_unregister_master(master); free_irq(mpc8xxx_spi->irq, mpc8xxx_spi); diff --git a/drivers/spi/spi-fsl-lib.h b/drivers/spi/spi-fsl-lib.h index 2fcbfd01d109..b4ed04e8862f 100644 --- a/drivers/spi/spi-fsl-lib.h +++ b/drivers/spi/spi-fsl-lib.h @@ -55,7 +55,6 @@ struct mpc8xxx_spi { u32(*get_tx) (struct mpc8xxx_spi *); /* hooks for different controller driver */ - void (*spi_do_one_msg) (struct spi_message *m); void (*spi_remove) (struct mpc8xxx_spi *mspi); unsigned int count; @@ -78,12 +77,6 @@ struct mpc8xxx_spi { int bits_per_word, int msb_first); #endif - struct workqueue_struct *workqueue; - struct work_struct work; - - struct list_head queue; - spinlock_t lock; - struct completion done; }; @@ -123,9 +116,8 @@ extern struct mpc8xxx_spi_probe_info *to_of_pinfo( struct fsl_spi_platform_data *pdata); extern int mpc8xxx_spi_bufs(struct mpc8xxx_spi *mspi, struct spi_transfer *t, unsigned int len); -extern int mpc8xxx_spi_transfer(struct spi_device *spi, struct spi_message *m); extern const char *mpc8xxx_spi_strmode(unsigned int flags); -extern int mpc8xxx_spi_probe(struct device *dev, struct resource *mem, +extern void mpc8xxx_spi_probe(struct device *dev, struct resource *mem, unsigned int irq); extern int mpc8xxx_spi_remove(struct device *dev); extern int of_mpc8xxx_spi_probe(struct platform_device *ofdev); diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index ed792880c9d6..6b94428a0223 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -353,7 +353,8 @@ static int fsl_spi_bufs(struct spi_device *spi, struct spi_transfer *t, return mpc8xxx_spi->count; } -static void fsl_spi_do_one_msg(struct spi_message *m) +static int fsl_spi_do_one_msg(struct spi_master *master, + struct spi_message *m) { struct spi_device *spi = m->spi; struct spi_transfer *t, *first; @@ -408,8 +409,7 @@ static void fsl_spi_do_one_msg(struct spi_message *m) } m->status = status; - if (m->complete) - m->complete(m->context); + spi_finalize_current_message(master); if (status || !cs_change) { ndelay(nsecs); @@ -417,6 +417,7 @@ static void fsl_spi_do_one_msg(struct spi_message *m) } fsl_spi_setup_transfer(spi, NULL); + return 0; } static int fsl_spi_setup(struct spi_device *spi) @@ -624,15 +625,13 @@ static struct spi_master * fsl_spi_probe(struct device *dev, dev_set_drvdata(dev, master); - ret = mpc8xxx_spi_probe(dev, mem, irq); - if (ret) - goto err_probe; + mpc8xxx_spi_probe(dev, mem, irq); master->setup = fsl_spi_setup; master->cleanup = fsl_spi_cleanup; + master->transfer_one_message = fsl_spi_do_one_msg; mpc8xxx_spi = spi_master_get_devdata(master); - mpc8xxx_spi->spi_do_one_msg = fsl_spi_do_one_msg; mpc8xxx_spi->spi_remove = fsl_spi_remove; mpc8xxx_spi->max_bits_per_word = 32; mpc8xxx_spi->type = fsl_spi_get_type(dev); @@ -704,7 +703,6 @@ free_irq: err_ioremap: fsl_spi_cpm_free(mpc8xxx_spi); err_cpm_init: -err_probe: spi_master_put(master); err: return ERR_PTR(ret); -- cgit v1.2.2 From 75506d0e0084ee7f074be3c5da6d5de98ce347b6 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 3 Dec 2014 07:56:19 +0100 Subject: spi: fsl-espi: add (un)prepare_transfer_hardware calls to save power if SPI is not in use Use (un)prepare_transfer_hardware calls to set fsl-espi to low-power idle if not in use. Reference manual states: "The eSPI is in a idle state and consumes minimal power. The eSPI BRG is not functioning and the input clock is disabled" Signed-off-by: Heiner Kallweit Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-espi.c | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-fsl-espi.c b/drivers/spi/spi-fsl-espi.c index 90be6405e789..56cadf13519e 100644 --- a/drivers/spi/spi-fsl-espi.c +++ b/drivers/spi/spi-fsl-espi.c @@ -588,6 +588,38 @@ static void fsl_espi_remove(struct mpc8xxx_spi *mspi) iounmap(mspi->reg_base); } +static int fsl_espi_suspend(struct spi_master *master) +{ + struct mpc8xxx_spi *mpc8xxx_spi; + struct fsl_espi_reg *reg_base; + u32 regval; + + mpc8xxx_spi = spi_master_get_devdata(master); + reg_base = mpc8xxx_spi->reg_base; + + regval = mpc8xxx_spi_read_reg(®_base->mode); + regval &= ~SPMODE_ENABLE; + mpc8xxx_spi_write_reg(®_base->mode, regval); + + return 0; +} + +static int fsl_espi_resume(struct spi_master *master) +{ + struct mpc8xxx_spi *mpc8xxx_spi; + struct fsl_espi_reg *reg_base; + u32 regval; + + mpc8xxx_spi = spi_master_get_devdata(master); + reg_base = mpc8xxx_spi->reg_base; + + regval = mpc8xxx_spi_read_reg(®_base->mode); + regval |= SPMODE_ENABLE; + mpc8xxx_spi_write_reg(®_base->mode, regval); + + return 0; +} + static struct spi_master * fsl_espi_probe(struct device *dev, struct resource *mem, unsigned int irq) { @@ -614,6 +646,8 @@ static struct spi_master * fsl_espi_probe(struct device *dev, master->setup = fsl_espi_setup; master->cleanup = fsl_espi_cleanup; master->transfer_one_message = fsl_espi_do_one_msg; + master->prepare_transfer_hardware = fsl_espi_resume; + master->unprepare_transfer_hardware = fsl_espi_suspend; mpc8xxx_spi = spi_master_get_devdata(master); mpc8xxx_spi->spi_remove = fsl_espi_remove; @@ -761,25 +795,15 @@ static int of_fsl_espi_remove(struct platform_device *dev) static int of_fsl_espi_suspend(struct device *dev) { struct spi_master *master = dev_get_drvdata(dev); - struct mpc8xxx_spi *mpc8xxx_spi; - struct fsl_espi_reg *reg_base; - u32 regval; int ret; - mpc8xxx_spi = spi_master_get_devdata(master); - reg_base = mpc8xxx_spi->reg_base; - ret = spi_master_suspend(master); if (ret) { dev_warn(dev, "cannot suspend master\n"); return ret; } - regval = mpc8xxx_spi_read_reg(®_base->mode); - regval &= ~SPMODE_ENABLE; - mpc8xxx_spi_write_reg(®_base->mode, regval); - - return 0; + return fsl_espi_suspend(master); } static int of_fsl_espi_resume(struct device *dev) -- cgit v1.2.2 From 75c410884aff0c502dcb34cc58997e67e9b61e5f Mon Sep 17 00:00:00 2001 From: Fabio Estevam Date: Thu, 4 Dec 2014 11:15:47 -0200 Subject: spi: spi-fsl-spi: Return an error code in fsl_spi_do_one_msg() Since commit c592becbe704127 ("spi: fsl-(e)spi: migrate to generic master queueing") the function fsl_spi_do_one_msg() is not void anymore, so return an error code to avoid the following buid warning: drivers/spi/spi-fsl-spi.c: In function 'fsl_spi_do_one_msg': >> drivers/spi/spi-fsl-spi.c:374:4: warning: 'return' with no value, in function returning non-void [-Wreturn-type] return; ^ Reported-by: kbuild test robot Signed-off-by: Fabio Estevam Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index 6b94428a0223..5e02c1f3e145 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -371,7 +371,7 @@ static int fsl_spi_do_one_msg(struct spi_master *master, status = -EINVAL; dev_err(&spi->dev, "bits_per_word/speed_hz should be same for the same SPI transfer\n"); - return; + return -EINVAL; } } -- cgit v1.2.2 From 1a7e06d2602f388485ba507bbee70766408db8ee Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 4 Dec 2014 20:28:11 +0100 Subject: spi: fsl-spi: remove unused variable assignment Remove an unused variable assignment. Signed-off-by: Heiner Kallweit Reviewed-by: Fabio Estevam Signed-off-by: Mark Brown --- drivers/spi/spi-fsl-spi.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-fsl-spi.c b/drivers/spi/spi-fsl-spi.c index 5e02c1f3e145..3b159364c459 100644 --- a/drivers/spi/spi-fsl-spi.c +++ b/drivers/spi/spi-fsl-spi.c @@ -368,7 +368,6 @@ static int fsl_spi_do_one_msg(struct spi_master *master, list_for_each_entry(t, &m->transfers, transfer_list) { if ((first->bits_per_word != t->bits_per_word) || (first->speed_hz != t->speed_hz)) { - status = -EINVAL; dev_err(&spi->dev, "bits_per_word/speed_hz should be same for the same SPI transfer\n"); return -EINVAL; -- cgit v1.2.2