diff --git a/Documentation/devicetree/bindings/spi/spi-orion.txt b/Documentation/devicetree/bindings/spi/spi-orion.txt index 98bc69815eb3f..4f629cc7634a9 100644 --- a/Documentation/devicetree/bindings/spi/spi-orion.txt +++ b/Documentation/devicetree/bindings/spi/spi-orion.txt @@ -8,7 +8,15 @@ Required properties: - "marvell,armada-380-spi", for the Armada 38x SoCs - "marvell,armada-390-spi", for the Armada 39x SoCs - "marvell,armada-xp-spi", for the Armada XP SoCs -- reg : offset and length of the register set for the device +- reg : offset and length of the register set for the device. + This property can optionally have additional entries to configure + the SPI direct access mode that some of the Marvell SoCs support + additionally to the normal indirect access (PIO) mode. The values + for the MBus "target" and "attribute" are defined in the Marvell + SoC "Functional Specifications" Manual in the chapter "Marvell + Core Processor Address Decoding". + The eight register sets following the control registers refer to + chip-select lines 0 through 7 respectively. - cell-index : Which of multiple SPI controllers is this. Optional properties: - interrupts : Is currently not used. @@ -23,3 +31,42 @@ Example: interrupts = <23>; status = "disabled"; }; + +Example with SPI direct mode support (optionally): + spi0: spi@10600 { + compatible = "marvell,orion-spi"; + #address-cells = <1>; + #size-cells = <0>; + cell-index = <0>; + reg = , /* control */ + , /* CS0 */ + , /* CS1 */ + , /* CS2 */ + , /* CS3 */ + , /* CS4 */ + , /* CS5 */ + , /* CS6 */ + ; /* CS7 */ + interrupts = <23>; + status = "disabled"; + }; + +To enable the direct mode, the board specific 'ranges' property in the +'soc' node needs to add the entries for the desired SPI controllers +and its chip-selects that are used in the direct mode instead of PIO +mode. Here an example for this (SPI controller 0, device 1 and SPI +controller 1, device 2 are used in direct mode. All other SPI device +are used in the default indirect (PIO) mode): + soc { + /* + * Enable the SPI direct access by configuring an entry + * here in the board-specific ranges property + */ + ranges = , /* internal regs */ + , /* BootROM */ + , /* SPI0-DEV1 */ + ; /* SPI1-DEV2 */ + +For further information on the MBus bindings, please see the MBus +DT documentation: +Documentation/devicetree/bindings/bus/mvebu-mbus.txt diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4b931ec8d90b6..d6fb8d4b77867 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -411,6 +411,7 @@ config SPI_OMAP24XX tristate "McSPI driver for OMAP" depends on HAS_DMA depends on ARCH_OMAP2PLUS || COMPILE_TEST + select SG_SPLIT help SPI master controller for OMAP24XX and later Multichannel SPI (McSPI) modules. diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 3c74d003535bb..185367ef65761 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_SPI_MT65XX) += spi-mt65xx.o obj-$(CONFIG_SPI_MXS) += spi-mxs.o obj-$(CONFIG_SPI_NUC900) += spi-nuc900.o obj-$(CONFIG_SPI_OC_TINY) += spi-oc-tiny.o +spi-octeon-objs := spi-cavium.o spi-cavium-octeon.o obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o diff --git a/drivers/spi/spi-cavium-octeon.c b/drivers/spi/spi-cavium-octeon.c new file mode 100644 index 0000000000000..ee4703e84622e --- /dev/null +++ b/drivers/spi/spi-cavium-octeon.c @@ -0,0 +1,104 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2011, 2012 Cavium, Inc. + */ + +#include +#include +#include +#include +#include + +#include + +#include "spi-cavium.h" + +static int octeon_spi_probe(struct platform_device *pdev) +{ + struct resource *res_mem; + void __iomem *reg_base; + struct spi_master *master; + struct octeon_spi *p; + int err = -ENOENT; + + master = spi_alloc_master(&pdev->dev, sizeof(struct octeon_spi)); + if (!master) + return -ENOMEM; + p = spi_master_get_devdata(master); + platform_set_drvdata(pdev, master); + + res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg_base = devm_ioremap_resource(&pdev->dev, res_mem); + if (IS_ERR(reg_base)) { + err = PTR_ERR(reg_base); + goto fail; + } + + p->register_base = reg_base; + p->sys_freq = octeon_get_io_clock_rate(); + + p->regs.config = 0; + p->regs.status = 0x08; + p->regs.tx = 0x10; + p->regs.data = 0x80; + + master->num_chipselect = 4; + master->mode_bits = SPI_CPHA | + SPI_CPOL | + SPI_CS_HIGH | + SPI_LSB_FIRST | + SPI_3WIRE; + + master->transfer_one_message = octeon_spi_transfer_one_message; + master->bits_per_word_mask = SPI_BPW_MASK(8); + master->max_speed_hz = OCTEON_SPI_MAX_CLOCK_HZ; + + master->dev.of_node = pdev->dev.of_node; + err = devm_spi_register_master(&pdev->dev, master); + if (err) { + dev_err(&pdev->dev, "register master failed: %d\n", err); + goto fail; + } + + dev_info(&pdev->dev, "OCTEON SPI bus driver\n"); + + return 0; +fail: + spi_master_put(master); + return err; +} + +static int octeon_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct octeon_spi *p = spi_master_get_devdata(master); + + /* Clear the CSENA* and put everything in a known state. */ + writeq(0, p->register_base + OCTEON_SPI_CFG(p)); + + return 0; +} + +static const struct of_device_id octeon_spi_match[] = { + { .compatible = "cavium,octeon-3010-spi", }, + {}, +}; +MODULE_DEVICE_TABLE(of, octeon_spi_match); + +static struct platform_driver octeon_spi_driver = { + .driver = { + .name = "spi-octeon", + .of_match_table = octeon_spi_match, + }, + .probe = octeon_spi_probe, + .remove = octeon_spi_remove, +}; + +module_platform_driver(octeon_spi_driver); + +MODULE_DESCRIPTION("Cavium, Inc. OCTEON SPI bus driver"); +MODULE_AUTHOR("David Daney"); +MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-cavium.c b/drivers/spi/spi-cavium.c new file mode 100644 index 0000000000000..5aaf21582cb5e --- /dev/null +++ b/drivers/spi/spi-cavium.c @@ -0,0 +1,151 @@ +/* + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * Copyright (C) 2011, 2012 Cavium, Inc. + */ + +#include +#include +#include +#include + +#include "spi-cavium.h" + +static void octeon_spi_wait_ready(struct octeon_spi *p) +{ + union cvmx_mpi_sts mpi_sts; + unsigned int loops = 0; + + do { + if (loops++) + __delay(500); + mpi_sts.u64 = readq(p->register_base + OCTEON_SPI_STS(p)); + } while (mpi_sts.s.busy); +} + +static int octeon_spi_do_transfer(struct octeon_spi *p, + struct spi_message *msg, + struct spi_transfer *xfer, + bool last_xfer) +{ + struct spi_device *spi = msg->spi; + union cvmx_mpi_cfg mpi_cfg; + union cvmx_mpi_tx mpi_tx; + unsigned int clkdiv; + int mode; + bool cpha, cpol; + const u8 *tx_buf; + u8 *rx_buf; + int len; + int i; + + mode = spi->mode; + cpha = mode & SPI_CPHA; + cpol = mode & SPI_CPOL; + + clkdiv = p->sys_freq / (2 * xfer->speed_hz); + + mpi_cfg.u64 = 0; + + mpi_cfg.s.clkdiv = clkdiv; + mpi_cfg.s.cshi = (mode & SPI_CS_HIGH) ? 1 : 0; + mpi_cfg.s.lsbfirst = (mode & SPI_LSB_FIRST) ? 1 : 0; + mpi_cfg.s.wireor = (mode & SPI_3WIRE) ? 1 : 0; + mpi_cfg.s.idlelo = cpha != cpol; + mpi_cfg.s.cslate = cpha ? 1 : 0; + mpi_cfg.s.enable = 1; + + if (spi->chip_select < 4) + p->cs_enax |= 1ull << (12 + spi->chip_select); + mpi_cfg.u64 |= p->cs_enax; + + if (mpi_cfg.u64 != p->last_cfg) { + p->last_cfg = mpi_cfg.u64; + writeq(mpi_cfg.u64, p->register_base + OCTEON_SPI_CFG(p)); + } + tx_buf = xfer->tx_buf; + rx_buf = xfer->rx_buf; + len = xfer->len; + while (len > OCTEON_SPI_MAX_BYTES) { + for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { + u8 d; + if (tx_buf) + d = *tx_buf++; + else + d = 0; + writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); + } + mpi_tx.u64 = 0; + mpi_tx.s.csid = spi->chip_select; + mpi_tx.s.leavecs = 1; + mpi_tx.s.txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0; + mpi_tx.s.totnum = OCTEON_SPI_MAX_BYTES; + writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p)); + + octeon_spi_wait_ready(p); + if (rx_buf) + for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { + u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); + *rx_buf++ = (u8)v; + } + len -= OCTEON_SPI_MAX_BYTES; + } + + for (i = 0; i < len; i++) { + u8 d; + if (tx_buf) + d = *tx_buf++; + else + d = 0; + writeq(d, p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); + } + + mpi_tx.u64 = 0; + mpi_tx.s.csid = spi->chip_select; + if (last_xfer) + mpi_tx.s.leavecs = xfer->cs_change; + else + mpi_tx.s.leavecs = !xfer->cs_change; + mpi_tx.s.txnum = tx_buf ? len : 0; + mpi_tx.s.totnum = len; + writeq(mpi_tx.u64, p->register_base + OCTEON_SPI_TX(p)); + + octeon_spi_wait_ready(p); + if (rx_buf) + for (i = 0; i < len; i++) { + u64 v = readq(p->register_base + OCTEON_SPI_DAT0(p) + (8 * i)); + *rx_buf++ = (u8)v; + } + + if (xfer->delay_usecs) + udelay(xfer->delay_usecs); + + return xfer->len; +} + +int octeon_spi_transfer_one_message(struct spi_master *master, + struct spi_message *msg) +{ + struct octeon_spi *p = spi_master_get_devdata(master); + unsigned int total_len = 0; + int status = 0; + struct spi_transfer *xfer; + + list_for_each_entry(xfer, &msg->transfers, transfer_list) { + bool last_xfer = list_is_last(&xfer->transfer_list, + &msg->transfers); + int r = octeon_spi_do_transfer(p, msg, xfer, last_xfer); + if (r < 0) { + status = r; + goto err; + } + total_len += r; + } +err: + msg->status = status; + msg->actual_length = total_len; + spi_finalize_current_message(master); + return status; +} diff --git a/arch/mips/include/asm/octeon/cvmx-mpi-defs.h b/drivers/spi/spi-cavium.h similarity index 84% rename from arch/mips/include/asm/octeon/cvmx-mpi-defs.h rename to drivers/spi/spi-cavium.h index 4615b102625b7..88c5f36e7ea75 100644 --- a/arch/mips/include/asm/octeon/cvmx-mpi-defs.h +++ b/drivers/spi/spi-cavium.h @@ -1,32 +1,33 @@ -/***********************license start*************** - * Author: Cavium Networks - * - * Contact: support@caviumnetworks.com - * This file is part of the OCTEON SDK - * - * Copyright (c) 2003-2012 Cavium Networks - * - * This file 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. - * - * This file is distributed in the hope that it will be useful, but - * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or - * NONINFRINGEMENT. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this file; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * or visit http://www.gnu.org/licenses/. - * - * This file may also be available under a different license from Cavium. - * Contact Cavium Networks for more information - ***********************license end**************************************/ +#ifndef __SPI_CAVIUM_H +#define __SPI_CAVIUM_H -#ifndef __CVMX_MPI_DEFS_H__ -#define __CVMX_MPI_DEFS_H__ +#define OCTEON_SPI_MAX_BYTES 9 +#define OCTEON_SPI_MAX_CLOCK_HZ 16000000 + +struct octeon_spi_regs { + int config; + int status; + int tx; + int data; +}; + +struct octeon_spi { + void __iomem *register_base; + u64 last_cfg; + u64 cs_enax; + int sys_freq; + struct octeon_spi_regs regs; +}; + +#define OCTEON_SPI_CFG(x) (x->regs.config) +#define OCTEON_SPI_STS(x) (x->regs.status) +#define OCTEON_SPI_TX(x) (x->regs.tx) +#define OCTEON_SPI_DAT0(x) (x->regs.data) + +int octeon_spi_transfer_one_message(struct spi_master *master, + struct spi_message *msg); + +/* MPI register descriptions */ #define CVMX_MPI_CFG (CVMX_ADD_IO_SEG(0x0001070000001000ull)) #define CVMX_MPI_DATX(offset) (CVMX_ADD_IO_SEG(0x0001070000001080ull) + ((offset) & 15) * 8) @@ -325,4 +326,4 @@ union cvmx_mpi_tx { struct cvmx_mpi_tx_cn61xx cnf71xx; }; -#endif +#endif /* __SPI_CAVIUM_H */ diff --git a/drivers/spi/spi-octeon.c b/drivers/spi/spi-octeon.c deleted file mode 100644 index 3b170093989fc..0000000000000 --- a/drivers/spi/spi-octeon.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - * - * Copyright (C) 2011, 2012 Cavium, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#define OCTEON_SPI_CFG 0 -#define OCTEON_SPI_STS 0x08 -#define OCTEON_SPI_TX 0x10 -#define OCTEON_SPI_DAT0 0x80 - -#define OCTEON_SPI_MAX_BYTES 9 - -#define OCTEON_SPI_MAX_CLOCK_HZ 16000000 - -struct octeon_spi { - u64 register_base; - u64 last_cfg; - u64 cs_enax; -}; - -static void octeon_spi_wait_ready(struct octeon_spi *p) -{ - union cvmx_mpi_sts mpi_sts; - unsigned int loops = 0; - - do { - if (loops++) - __delay(500); - mpi_sts.u64 = cvmx_read_csr(p->register_base + OCTEON_SPI_STS); - } while (mpi_sts.s.busy); -} - -static int octeon_spi_do_transfer(struct octeon_spi *p, - struct spi_message *msg, - struct spi_transfer *xfer, - bool last_xfer) -{ - struct spi_device *spi = msg->spi; - union cvmx_mpi_cfg mpi_cfg; - union cvmx_mpi_tx mpi_tx; - unsigned int clkdiv; - unsigned int speed_hz; - int mode; - bool cpha, cpol; - const u8 *tx_buf; - u8 *rx_buf; - int len; - int i; - - mode = spi->mode; - cpha = mode & SPI_CPHA; - cpol = mode & SPI_CPOL; - - speed_hz = xfer->speed_hz; - - clkdiv = octeon_get_io_clock_rate() / (2 * speed_hz); - - mpi_cfg.u64 = 0; - - mpi_cfg.s.clkdiv = clkdiv; - mpi_cfg.s.cshi = (mode & SPI_CS_HIGH) ? 1 : 0; - mpi_cfg.s.lsbfirst = (mode & SPI_LSB_FIRST) ? 1 : 0; - mpi_cfg.s.wireor = (mode & SPI_3WIRE) ? 1 : 0; - mpi_cfg.s.idlelo = cpha != cpol; - mpi_cfg.s.cslate = cpha ? 1 : 0; - mpi_cfg.s.enable = 1; - - if (spi->chip_select < 4) - p->cs_enax |= 1ull << (12 + spi->chip_select); - mpi_cfg.u64 |= p->cs_enax; - - if (mpi_cfg.u64 != p->last_cfg) { - p->last_cfg = mpi_cfg.u64; - cvmx_write_csr(p->register_base + OCTEON_SPI_CFG, mpi_cfg.u64); - } - tx_buf = xfer->tx_buf; - rx_buf = xfer->rx_buf; - len = xfer->len; - while (len > OCTEON_SPI_MAX_BYTES) { - for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { - u8 d; - if (tx_buf) - d = *tx_buf++; - else - d = 0; - cvmx_write_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i), d); - } - mpi_tx.u64 = 0; - mpi_tx.s.csid = spi->chip_select; - mpi_tx.s.leavecs = 1; - mpi_tx.s.txnum = tx_buf ? OCTEON_SPI_MAX_BYTES : 0; - mpi_tx.s.totnum = OCTEON_SPI_MAX_BYTES; - cvmx_write_csr(p->register_base + OCTEON_SPI_TX, mpi_tx.u64); - - octeon_spi_wait_ready(p); - if (rx_buf) - for (i = 0; i < OCTEON_SPI_MAX_BYTES; i++) { - u64 v = cvmx_read_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i)); - *rx_buf++ = (u8)v; - } - len -= OCTEON_SPI_MAX_BYTES; - } - - for (i = 0; i < len; i++) { - u8 d; - if (tx_buf) - d = *tx_buf++; - else - d = 0; - cvmx_write_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i), d); - } - - mpi_tx.u64 = 0; - mpi_tx.s.csid = spi->chip_select; - if (last_xfer) - mpi_tx.s.leavecs = xfer->cs_change; - else - mpi_tx.s.leavecs = !xfer->cs_change; - mpi_tx.s.txnum = tx_buf ? len : 0; - mpi_tx.s.totnum = len; - cvmx_write_csr(p->register_base + OCTEON_SPI_TX, mpi_tx.u64); - - octeon_spi_wait_ready(p); - if (rx_buf) - for (i = 0; i < len; i++) { - u64 v = cvmx_read_csr(p->register_base + OCTEON_SPI_DAT0 + (8 * i)); - *rx_buf++ = (u8)v; - } - - if (xfer->delay_usecs) - udelay(xfer->delay_usecs); - - return xfer->len; -} - -static int octeon_spi_transfer_one_message(struct spi_master *master, - struct spi_message *msg) -{ - struct octeon_spi *p = spi_master_get_devdata(master); - unsigned int total_len = 0; - int status = 0; - struct spi_transfer *xfer; - - list_for_each_entry(xfer, &msg->transfers, transfer_list) { - bool last_xfer = list_is_last(&xfer->transfer_list, - &msg->transfers); - int r = octeon_spi_do_transfer(p, msg, xfer, last_xfer); - if (r < 0) { - status = r; - goto err; - } - total_len += r; - } -err: - msg->status = status; - msg->actual_length = total_len; - spi_finalize_current_message(master); - return status; -} - -static int octeon_spi_probe(struct platform_device *pdev) -{ - struct resource *res_mem; - void __iomem *reg_base; - struct spi_master *master; - struct octeon_spi *p; - int err = -ENOENT; - - master = spi_alloc_master(&pdev->dev, sizeof(struct octeon_spi)); - if (!master) - return -ENOMEM; - p = spi_master_get_devdata(master); - platform_set_drvdata(pdev, master); - - res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - reg_base = devm_ioremap_resource(&pdev->dev, res_mem); - if (IS_ERR(reg_base)) { - err = PTR_ERR(reg_base); - goto fail; - } - - p->register_base = (u64)reg_base; - - master->num_chipselect = 4; - master->mode_bits = SPI_CPHA | - SPI_CPOL | - SPI_CS_HIGH | - SPI_LSB_FIRST | - SPI_3WIRE; - - master->transfer_one_message = octeon_spi_transfer_one_message; - master->bits_per_word_mask = SPI_BPW_MASK(8); - master->max_speed_hz = OCTEON_SPI_MAX_CLOCK_HZ; - - master->dev.of_node = pdev->dev.of_node; - err = devm_spi_register_master(&pdev->dev, master); - if (err) { - dev_err(&pdev->dev, "register master failed: %d\n", err); - goto fail; - } - - dev_info(&pdev->dev, "OCTEON SPI bus driver\n"); - - return 0; -fail: - spi_master_put(master); - return err; -} - -static int octeon_spi_remove(struct platform_device *pdev) -{ - struct spi_master *master = platform_get_drvdata(pdev); - struct octeon_spi *p = spi_master_get_devdata(master); - u64 register_base = p->register_base; - - /* Clear the CSENA* and put everything in a known state. */ - cvmx_write_csr(register_base + OCTEON_SPI_CFG, 0); - - return 0; -} - -static const struct of_device_id octeon_spi_match[] = { - { .compatible = "cavium,octeon-3010-spi", }, - {}, -}; -MODULE_DEVICE_TABLE(of, octeon_spi_match); - -static struct platform_driver octeon_spi_driver = { - .driver = { - .name = "spi-octeon", - .of_match_table = octeon_spi_match, - }, - .probe = octeon_spi_probe, - .remove = octeon_spi_remove, -}; - -module_platform_driver(octeon_spi_driver); - -MODULE_DESCRIPTION("Cavium, Inc. OCTEON SPI bus driver"); -MODULE_AUTHOR("David Daney"); -MODULE_LICENSE("GPL"); diff --git a/drivers/spi/spi-omap2-mcspi.c b/drivers/spi/spi-omap2-mcspi.c index 1d237e93a2895..d5157b2222ce1 100644 --- a/drivers/spi/spi-omap2-mcspi.c +++ b/drivers/spi/spi-omap2-mcspi.c @@ -419,16 +419,13 @@ static void omap2_mcspi_tx_dma(struct spi_device *spi, if (mcspi_dma->dma_tx) { struct dma_async_tx_descriptor *tx; - struct scatterlist sg; dmaengine_slave_config(mcspi_dma->dma_tx, &cfg); - sg_init_table(&sg, 1); - sg_dma_address(&sg) = xfer->tx_dma; - sg_dma_len(&sg) = xfer->len; - - tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, &sg, 1, - DMA_MEM_TO_DEV, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + tx = dmaengine_prep_slave_sg(mcspi_dma->dma_tx, xfer->tx_sg.sgl, + xfer->tx_sg.nents, + DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (tx) { tx->callback = omap2_mcspi_tx_callback; tx->callback_param = spi; @@ -449,7 +446,10 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, { struct omap2_mcspi *mcspi; struct omap2_mcspi_dma *mcspi_dma; - unsigned int count, dma_count; + unsigned int count, transfer_reduction = 0; + struct scatterlist *sg_out[2]; + int nb_sizes = 0, out_mapped_nents[2], ret, x; + size_t sizes[2]; u32 l; int elements = 0; int word_len, element_count; @@ -457,10 +457,14 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, mcspi = spi_master_get_devdata(spi->master); mcspi_dma = &mcspi->dma_channels[spi->chip_select]; count = xfer->len; - dma_count = xfer->len; + /* + * In the "End-of-Transfer Procedure" section for DMA RX in OMAP35x TRM + * it mentions reducing DMA transfer length by one element in master + * normal mode. + */ if (mcspi->fifo_depth == 0) - dma_count -= es; + transfer_reduction = es; word_len = cs->word_len; l = mcspi_cached_chconf0(spi); @@ -474,20 +478,46 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, if (mcspi_dma->dma_rx) { struct dma_async_tx_descriptor *tx; - struct scatterlist sg; dmaengine_slave_config(mcspi_dma->dma_rx, &cfg); + /* + * Reduce DMA transfer length by one more if McSPI is + * configured in turbo mode. + */ if ((l & OMAP2_MCSPI_CHCONF_TURBO) && mcspi->fifo_depth == 0) - dma_count -= es; + transfer_reduction += es; + + if (transfer_reduction) { + /* Split sgl into two. The second sgl won't be used. */ + sizes[0] = count - transfer_reduction; + sizes[1] = transfer_reduction; + nb_sizes = 2; + } else { + /* + * Don't bother splitting the sgl. This essentially + * clones the original sgl. + */ + sizes[0] = count; + nb_sizes = 1; + } + + ret = sg_split(xfer->rx_sg.sgl, xfer->rx_sg.nents, + 0, nb_sizes, + sizes, + sg_out, out_mapped_nents, + GFP_KERNEL); - sg_init_table(&sg, 1); - sg_dma_address(&sg) = xfer->rx_dma; - sg_dma_len(&sg) = dma_count; + if (ret < 0) { + dev_err(&spi->dev, "sg_split failed\n"); + return 0; + } - tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, &sg, 1, - DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT | - DMA_CTRL_ACK); + tx = dmaengine_prep_slave_sg(mcspi_dma->dma_rx, + sg_out[0], + out_mapped_nents[0], + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (tx) { tx->callback = omap2_mcspi_rx_callback; tx->callback_param = spi; @@ -501,12 +531,17 @@ omap2_mcspi_rx_dma(struct spi_device *spi, struct spi_transfer *xfer, omap2_mcspi_set_dma_req(spi, 1, 1); wait_for_completion(&mcspi_dma->dma_rx_completion); - dma_unmap_single(mcspi->dev, xfer->rx_dma, count, - DMA_FROM_DEVICE); + + for (x = 0; x < nb_sizes; x++) + kfree(sg_out[x]); if (mcspi->fifo_depth > 0) return count; + /* + * Due to the DMA transfer length reduction the missing bytes must + * be read manually to receive all of the expected data. + */ omap2_mcspi_set_enable(spi, 0); elements = element_count - 1; @@ -615,8 +650,6 @@ omap2_mcspi_txrx_dma(struct spi_device *spi, struct spi_transfer *xfer) if (tx != NULL) { wait_for_completion(&mcspi_dma->dma_tx_completion); - dma_unmap_single(mcspi->dev, xfer->tx_dma, xfer->len, - DMA_TO_DEVICE); if (mcspi->fifo_depth > 0) { irqstat_reg = mcspi->base + OMAP2_MCSPI_IRQSTATUS; @@ -1074,8 +1107,9 @@ static void omap2_mcspi_cleanup(struct spi_device *spi) gpio_free(spi->cs_gpio); } -static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi, - struct spi_device *spi, struct spi_transfer *t) +static int omap2_mcspi_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *t) { /* We only enable one channel at a time -- the one whose message is @@ -1085,7 +1119,7 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi, * chipselect with the FORCE bit ... CS != channel enable. */ - struct spi_master *master; + struct omap2_mcspi *mcspi; struct omap2_mcspi_dma *mcspi_dma; struct omap2_mcspi_cs *cs; struct omap2_mcspi_device_config *cd; @@ -1093,7 +1127,7 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi, int status = 0; u32 chconf; - master = spi->master; + mcspi = spi_master_get_devdata(master); mcspi_dma = mcspi->dma_channels + spi->chip_select; cs = spi->controller_state; cd = spi->controller_data; @@ -1153,7 +1187,8 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi, unsigned count; if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) && - (t->len >= DMA_MIN_BYTES)) + master->cur_msg_mapped && + master->can_dma(master, spi, t)) omap2_mcspi_set_fifo(spi, t, 1); omap2_mcspi_set_enable(spi, 1); @@ -1164,7 +1199,8 @@ static int omap2_mcspi_work_one(struct omap2_mcspi *mcspi, + OMAP2_MCSPI_TX0); if ((mcspi_dma->dma_rx && mcspi_dma->dma_tx) && - (t->len >= DMA_MIN_BYTES)) + master->cur_msg_mapped && + master->can_dma(master, spi, t)) count = omap2_mcspi_txrx_dma(spi, t); else count = omap2_mcspi_txrx_pio(spi, t); @@ -1233,55 +1269,11 @@ static int omap2_mcspi_prepare_message(struct spi_master *master, return 0; } -static int omap2_mcspi_transfer_one(struct spi_master *master, - struct spi_device *spi, struct spi_transfer *t) +static bool omap2_mcspi_can_dma(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) { - struct omap2_mcspi *mcspi; - struct omap2_mcspi_dma *mcspi_dma; - const void *tx_buf = t->tx_buf; - void *rx_buf = t->rx_buf; - unsigned len = t->len; - - mcspi = spi_master_get_devdata(master); - mcspi_dma = mcspi->dma_channels + spi->chip_select; - - if ((len && !(rx_buf || tx_buf))) { - dev_dbg(mcspi->dev, "transfer: %d Hz, %d %s%s, %d bpw\n", - t->speed_hz, - len, - tx_buf ? "tx" : "", - rx_buf ? "rx" : "", - t->bits_per_word); - return -EINVAL; - } - - if (len < DMA_MIN_BYTES) - goto skip_dma_map; - - if (mcspi_dma->dma_tx && tx_buf != NULL) { - t->tx_dma = dma_map_single(mcspi->dev, (void *) tx_buf, - len, DMA_TO_DEVICE); - if (dma_mapping_error(mcspi->dev, t->tx_dma)) { - dev_dbg(mcspi->dev, "dma %cX %d bytes error\n", - 'T', len); - return -EINVAL; - } - } - if (mcspi_dma->dma_rx && rx_buf != NULL) { - t->rx_dma = dma_map_single(mcspi->dev, rx_buf, t->len, - DMA_FROM_DEVICE); - if (dma_mapping_error(mcspi->dev, t->rx_dma)) { - dev_dbg(mcspi->dev, "dma %cX %d bytes error\n", - 'R', len); - if (tx_buf != NULL) - dma_unmap_single(mcspi->dev, t->tx_dma, - len, DMA_TO_DEVICE); - return -EINVAL; - } - } - -skip_dma_map: - return omap2_mcspi_work_one(mcspi, spi, t); + return (xfer->len >= DMA_MIN_BYTES); } static int omap2_mcspi_master_setup(struct omap2_mcspi *mcspi) @@ -1361,6 +1353,7 @@ static int omap2_mcspi_probe(struct platform_device *pdev) master->setup = omap2_mcspi_setup; master->auto_runtime_pm = true; master->prepare_message = omap2_mcspi_prepare_message; + master->can_dma = omap2_mcspi_can_dma; master->transfer_one = omap2_mcspi_transfer_one; master->set_cs = omap2_mcspi_set_cs; master->cleanup = omap2_mcspi_cleanup; diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index a87cfd4ba17b3..ded37025b4458 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,9 @@ #define ORION_SPI_INT_CAUSE_REG 0x10 #define ORION_SPI_TIMING_PARAMS_REG 0x18 +/* Register for the "Direct Mode" */ +#define SPI_DIRECT_WRITE_CONFIG_REG 0x20 + #define ORION_SPI_TMISO_SAMPLE_MASK (0x3 << 6) #define ORION_SPI_TMISO_SAMPLE_1 (1 << 6) #define ORION_SPI_TMISO_SAMPLE_2 (2 << 6) @@ -78,11 +82,18 @@ struct orion_spi_dev { bool is_errata_50mhz_ac; }; +struct orion_direct_acc { + void __iomem *vaddr; + u32 size; +}; + struct orion_spi { struct spi_master *master; void __iomem *base; struct clk *clk; const struct orion_spi_dev *devdata; + + struct orion_direct_acc direct_access[ORION_NUM_CHIPSELECTS]; }; static inline void __iomem *spi_reg(struct orion_spi *orion_spi, u32 reg) @@ -372,10 +383,39 @@ orion_spi_write_read(struct spi_device *spi, struct spi_transfer *xfer) { unsigned int count; int word_len; + struct orion_spi *orion_spi; + int cs = spi->chip_select; word_len = spi->bits_per_word; count = xfer->len; + orion_spi = spi_master_get_devdata(spi->master); + + /* + * Use SPI direct write mode if base address is available. Otherwise + * fall back to PIO mode for this transfer. + */ + if ((orion_spi->direct_access[cs].vaddr) && (xfer->tx_buf) && + (word_len == 8)) { + unsigned int cnt = count / 4; + unsigned int rem = count % 4; + + /* + * Send the TX-data to the SPI device via the direct + * mapped address window + */ + iowrite32_rep(orion_spi->direct_access[cs].vaddr, + xfer->tx_buf, cnt); + if (rem) { + u32 *buf = (u32 *)xfer->tx_buf; + + iowrite8_rep(orion_spi->direct_access[cs].vaddr, + &buf[cnt], rem); + } + + return count; + } + if (word_len == 8) { const u8 *tx = xfer->tx_buf; u8 *rx = xfer->rx_buf; @@ -425,6 +465,10 @@ static int orion_spi_reset(struct orion_spi *orion_spi) { /* Verify that the CS is deasserted */ orion_spi_clrbits(orion_spi, ORION_SPI_IF_CTRL_REG, 0x1); + + /* Don't deassert CS between the direct mapped SPI transfers */ + writel(0, spi_reg(orion_spi, SPI_DIRECT_WRITE_CONFIG_REG)); + return 0; } @@ -504,6 +548,7 @@ static int orion_spi_probe(struct platform_device *pdev) struct resource *r; unsigned long tclk_hz; int status = 0; + struct device_node *np; master = spi_alloc_master(&pdev->dev, sizeof(*spi)); if (master == NULL) { @@ -576,6 +621,49 @@ static int orion_spi_probe(struct platform_device *pdev) goto out_rel_clk; } + /* Scan all SPI devices of this controller for direct mapped devices */ + for_each_available_child_of_node(pdev->dev.of_node, np) { + u32 cs; + + /* Get chip-select number from the "reg" property */ + status = of_property_read_u32(np, "reg", &cs); + if (status) { + dev_err(&pdev->dev, + "%s has no valid 'reg' property (%d)\n", + np->full_name, status); + status = 0; + continue; + } + + /* + * Check if an address is configured for this SPI device. If + * not, the MBus mapping via the 'ranges' property in the 'soc' + * node is not configured and this device should not use the + * direct mode. In this case, just continue with the next + * device. + */ + status = of_address_to_resource(pdev->dev.of_node, cs + 1, r); + if (status) + continue; + + /* + * Only map one page for direct access. This is enough for the + * simple TX transfer which only writes to the first word. + * This needs to get extended for the direct SPI-NOR / SPI-NAND + * support, once this gets implemented. + */ + spi->direct_access[cs].vaddr = devm_ioremap(&pdev->dev, + r->start, + PAGE_SIZE); + if (!spi->direct_access[cs].vaddr) { + status = -ENOMEM; + goto out_rel_clk; + } + spi->direct_access[cs].size = PAGE_SIZE; + + dev_info(&pdev->dev, "CS%d configured for direct access\n", cs); + } + pm_runtime_set_active(&pdev->dev); pm_runtime_use_autosuspend(&pdev->dev); pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT); diff --git a/drivers/spi/spi-pic32-sqi.c b/drivers/spi/spi-pic32-sqi.c index ca3c8d94b2902..c41abddab3189 100644 --- a/drivers/spi/spi-pic32-sqi.c +++ b/drivers/spi/spi-pic32-sqi.c @@ -354,6 +354,7 @@ static int pic32_sqi_one_message(struct spi_master *master, struct spi_transfer *xfer; struct pic32_sqi *sqi; int ret = 0, mode; + unsigned long timeout; u32 val; sqi = spi_master_get_devdata(master); @@ -419,10 +420,10 @@ static int pic32_sqi_one_message(struct spi_master *master, writel(val, sqi->regs + PESQI_BD_CTRL_REG); /* wait for xfer completion */ - ret = wait_for_completion_timeout(&sqi->xfer_done, 5 * HZ); - if (ret <= 0) { + timeout = wait_for_completion_timeout(&sqi->xfer_done, 5 * HZ); + if (timeout == 0) { dev_err(&sqi->master->dev, "wait timedout/interrupted\n"); - ret = -EIO; + ret = -ETIMEDOUT; msg->status = ret; } else { /* success */ diff --git a/drivers/spi/spi-pic32.c b/drivers/spi/spi-pic32.c index 73db87f805a1f..fefb688a34328 100644 --- a/drivers/spi/spi-pic32.c +++ b/drivers/spi/spi-pic32.c @@ -507,6 +507,7 @@ static int pic32_spi_one_transfer(struct spi_master *master, { struct pic32_spi *pic32s; bool dma_issued = false; + unsigned long timeout; int ret; pic32s = spi_master_get_devdata(master); @@ -553,8 +554,8 @@ static int pic32_spi_one_transfer(struct spi_master *master, } /* wait for completion */ - ret = wait_for_completion_timeout(&pic32s->xfer_done, 2 * HZ); - if (ret <= 0) { + timeout = wait_for_completion_timeout(&pic32s->xfer_done, 2 * HZ); + if (timeout == 0) { dev_err(&spi->dev, "wait error/timedout\n"); if (dma_issued) { dmaengine_terminate_all(master->dma_rx);