diff options
Diffstat (limited to 'drivers/spi')
-rw-r--r-- | drivers/spi/Kconfig | 14 | ||||
-rw-r--r-- | drivers/spi/Makefile | 1 | ||||
-rw-r--r-- | drivers/spi/spi-orion.c | 68 | ||||
-rw-r--r-- | drivers/spi/spi-pl022.c | 2 | ||||
-rw-r--r-- | drivers/spi/spi-qup.c | 36 | ||||
-rw-r--r-- | drivers/spi/spi-rockchip.c | 837 | ||||
-rw-r--r-- | drivers/spi/spi-rspi.c | 45 |
7 files changed, 959 insertions, 44 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 213b5cbb9dcc..20bd055ea2d1 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -382,9 +382,21 @@ config SPI_PXA2XX config SPI_PXA2XX_PCI def_tristate SPI_PXA2XX && PCI +config SPI_ROCKCHIP + tristate "Rockchip SPI controller driver" + depends on ARM || ARM64 || AVR32 || HEXAGON || MIPS || SUPERH + help + This selects a driver for Rockchip SPI controller. + + If you say yes to this option, support will be included for + RK3066, RK3188 and RK3288 families of SPI controller. + Rockchip SPI controller support DMA transport and PIO mode. + The main usecase of this controller is to use spi flash as boot + device. + config SPI_RSPI tristate "Renesas RSPI/QSPI controller" - depends on (SUPERH && SH_DMAE_BASE) || ARCH_SHMOBILE + depends on SUPERH || ARCH_SHMOBILE || COMPILE_TEST help SPI driver for Renesas RSPI and QSPI blocks. diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 929c9f5eac01..762da0741148 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -61,6 +61,7 @@ spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o obj-$(CONFIG_SPI_QUP) += spi-qup.o +obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o obj-$(CONFIG_SPI_RSPI) += spi-rspi.o obj-$(CONFIG_SPI_S3C24XX) += spi-s3c24xx-hw.o spi-s3c24xx-hw-y := spi-s3c24xx.o diff --git a/drivers/spi/spi-orion.c b/drivers/spi/spi-orion.c index c206a4ad83cd..c4675fa8b645 100644 --- a/drivers/spi/spi-orion.c +++ b/drivers/spi/spi-orion.c @@ -16,6 +16,7 @@ #include <linux/io.h> #include <linux/spi/spi.h> #include <linux/module.h> +#include <linux/pm_runtime.h> #include <linux/of.h> #include <linux/clk.h> #include <linux/sizes.h> @@ -23,6 +24,9 @@ #define DRIVER_NAME "orion_spi" +/* Runtime PM autosuspend timeout: PM is fairly light on this driver */ +#define SPI_AUTOSUSPEND_TIMEOUT 200 + #define ORION_NUM_CHIPSELECTS 1 /* only one slave is supported*/ #define ORION_SPI_WAIT_RDY_MAX_LOOP 2000 /* in usec */ @@ -277,7 +281,6 @@ out: return xfer->len - count; } - static int orion_spi_transfer_one_message(struct spi_master *master, struct spi_message *m) { @@ -368,6 +371,7 @@ static int orion_spi_probe(struct platform_device *pdev) master->transfer_one_message = orion_spi_transfer_one_message; master->num_chipselect = ORION_NUM_CHIPSELECTS; master->bits_per_word_mask = SPI_BPW_MASK(8) | SPI_BPW_MASK(16); + master->auto_runtime_pm = true; platform_set_drvdata(pdev, master); @@ -380,8 +384,10 @@ static int orion_spi_probe(struct platform_device *pdev) goto out; } - clk_prepare(spi->clk); - clk_enable(spi->clk); + status = clk_prepare_enable(spi->clk); + if (status) + goto out; + tclk_hz = clk_get_rate(spi->clk); master->max_speed_hz = DIV_ROUND_UP(tclk_hz, 4); master->min_speed_hz = DIV_ROUND_UP(tclk_hz, 30); @@ -393,16 +399,27 @@ static int orion_spi_probe(struct platform_device *pdev) goto out_rel_clk; } - if (orion_spi_reset(spi) < 0) - goto out_rel_clk; + pm_runtime_set_active(&pdev->dev); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_autosuspend_delay(&pdev->dev, SPI_AUTOSUSPEND_TIMEOUT); + pm_runtime_enable(&pdev->dev); + + status = orion_spi_reset(spi); + if (status < 0) + goto out_rel_pm; + + pm_runtime_mark_last_busy(&pdev->dev); + pm_runtime_put_autosuspend(&pdev->dev); master->dev.of_node = pdev->dev.of_node; - status = devm_spi_register_master(&pdev->dev, master); + status = spi_register_master(master); if (status < 0) - goto out_rel_clk; + goto out_rel_pm; return status; +out_rel_pm: + pm_runtime_disable(&pdev->dev); out_rel_clk: clk_disable_unprepare(spi->clk); out: @@ -413,19 +430,45 @@ out: static int orion_spi_remove(struct platform_device *pdev) { - struct spi_master *master; - struct orion_spi *spi; - - master = platform_get_drvdata(pdev); - spi = spi_master_get_devdata(master); + struct spi_master *master = platform_get_drvdata(pdev); + struct orion_spi *spi = spi_master_get_devdata(master); + pm_runtime_get_sync(&pdev->dev); clk_disable_unprepare(spi->clk); + spi_unregister_master(master); + pm_runtime_disable(&pdev->dev); + return 0; } MODULE_ALIAS("platform:" DRIVER_NAME); +#ifdef CONFIG_PM_RUNTIME +static int orion_spi_runtime_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct orion_spi *spi = spi_master_get_devdata(master); + + clk_disable_unprepare(spi->clk); + return 0; +} + +static int orion_spi_runtime_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct orion_spi *spi = spi_master_get_devdata(master); + + return clk_prepare_enable(spi->clk); +} +#endif + +static const struct dev_pm_ops orion_spi_pm_ops = { + SET_RUNTIME_PM_OPS(orion_spi_runtime_suspend, + orion_spi_runtime_resume, + NULL) +}; + static const struct of_device_id orion_spi_of_match_table[] = { { .compatible = "marvell,orion-spi", }, {} @@ -436,6 +479,7 @@ static struct platform_driver orion_spi_driver = { .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, + .pm = &orion_spi_pm_ops, .of_match_table = of_match_ptr(orion_spi_of_match_table), }, .probe = orion_spi_probe, diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 66d2ae21e78e..1189cfd96477 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -1417,7 +1417,7 @@ static void do_interrupt_dma_transfer(struct pl022 *pl022) * Default is to enable all interrupts except RX - * this will be enabled once TX is complete */ - u32 irqflags = ENABLE_ALL_INTERRUPTS & ~SSP_IMSC_MASK_RXIM; + u32 irqflags = (u32)(ENABLE_ALL_INTERRUPTS & ~SSP_IMSC_MASK_RXIM); /* Enable target chip, if not already active */ if (!pl022->next_msg_cs_active) diff --git a/drivers/spi/spi-qup.c b/drivers/spi/spi-qup.c index c08da380cb23..9f83d2950748 100644 --- a/drivers/spi/spi-qup.c +++ b/drivers/spi/spi-qup.c @@ -142,6 +142,7 @@ struct spi_qup { int w_size; /* bytes per SPI word */ int tx_bytes; int rx_bytes; + int qup_v1; }; @@ -420,7 +421,9 @@ static int spi_qup_io_config(struct spi_device *spi, struct spi_transfer *xfer) config |= QUP_CONFIG_SPI_MODE; writel_relaxed(config, controller->base + QUP_CONFIG); - writel_relaxed(0, controller->base + QUP_OPERATIONAL_MASK); + /* only write to OPERATIONAL_MASK when register is present */ + if (!controller->qup_v1) + writel_relaxed(0, controller->base + QUP_OPERATIONAL_MASK); return 0; } @@ -486,7 +489,7 @@ static int spi_qup_probe(struct platform_device *pdev) struct resource *res; struct device *dev; void __iomem *base; - u32 data, max_freq, iomode; + u32 max_freq, iomode; int ret, irq, size; dev = &pdev->dev; @@ -529,15 +532,6 @@ static int spi_qup_probe(struct platform_device *pdev) return ret; } - data = readl_relaxed(base + QUP_HW_VERSION); - - if (data < QUP_HW_VERSION_2_1_1) { - clk_disable_unprepare(cclk); - clk_disable_unprepare(iclk); - dev_err(dev, "v.%08x is not supported\n", data); - return -ENXIO; - } - master = spi_alloc_master(dev, sizeof(struct spi_qup)); if (!master) { clk_disable_unprepare(cclk); @@ -570,6 +564,10 @@ static int spi_qup_probe(struct platform_device *pdev) controller->cclk = cclk; controller->irq = irq; + /* set v1 flag if device is version 1 */ + if (of_device_is_compatible(dev->of_node, "qcom,spi-qup-v1.1.1")) + controller->qup_v1 = 1; + spin_lock_init(&controller->lock); init_completion(&controller->done); @@ -593,8 +591,8 @@ static int spi_qup_probe(struct platform_device *pdev) size = QUP_IO_M_INPUT_FIFO_SIZE(iomode); controller->in_fifo_sz = controller->in_blk_sz * (2 << size); - dev_info(dev, "v.%08x IN:block:%d, fifo:%d, OUT:block:%d, fifo:%d\n", - data, controller->in_blk_sz, controller->in_fifo_sz, + dev_info(dev, "IN:block:%d, fifo:%d, OUT:block:%d, fifo:%d\n", + controller->in_blk_sz, controller->in_fifo_sz, controller->out_blk_sz, controller->out_fifo_sz); writel_relaxed(1, base + QUP_SW_RESET); @@ -607,10 +605,19 @@ static int spi_qup_probe(struct platform_device *pdev) writel_relaxed(0, base + QUP_OPERATIONAL); writel_relaxed(0, base + QUP_IO_M_MODES); - writel_relaxed(0, base + QUP_OPERATIONAL_MASK); + + if (!controller->qup_v1) + writel_relaxed(0, base + QUP_OPERATIONAL_MASK); + writel_relaxed(SPI_ERROR_CLK_UNDER_RUN | SPI_ERROR_CLK_OVER_RUN, base + SPI_ERROR_FLAGS_EN); + /* if earlier version of the QUP, disable INPUT_OVERRUN */ + if (controller->qup_v1) + writel_relaxed(QUP_ERROR_OUTPUT_OVER_RUN | + QUP_ERROR_INPUT_UNDER_RUN | QUP_ERROR_OUTPUT_UNDER_RUN, + base + QUP_ERROR_FLAGS_EN); + writel_relaxed(0, base + SPI_CONFIG); writel_relaxed(SPI_IO_C_NO_TRI_STATE, base + SPI_IO_CONTROL); @@ -732,6 +739,7 @@ static int spi_qup_remove(struct platform_device *pdev) } static const struct of_device_id spi_qup_dt_match[] = { + { .compatible = "qcom,spi-qup-v1.1.1", }, { .compatible = "qcom,spi-qup-v2.1.1", }, { .compatible = "qcom,spi-qup-v2.2.1", }, { } diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c new file mode 100644 index 000000000000..c0743604b906 --- /dev/null +++ b/drivers/spi/spi-rockchip.c @@ -0,0 +1,837 @@ +/* + * Copyright (c) 2014, Fuzhou Rockchip Electronics Co., Ltd + * Author: Addy Ke <addy.ke@rock-chips.com> + * + * 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. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/scatterlist.h> +#include <linux/of.h> +#include <linux/pm_runtime.h> +#include <linux/io.h> +#include <linux/dmaengine.h> + +#define DRIVER_NAME "rockchip-spi" + +/* SPI register offsets */ +#define ROCKCHIP_SPI_CTRLR0 0x0000 +#define ROCKCHIP_SPI_CTRLR1 0x0004 +#define ROCKCHIP_SPI_SSIENR 0x0008 +#define ROCKCHIP_SPI_SER 0x000c +#define ROCKCHIP_SPI_BAUDR 0x0010 +#define ROCKCHIP_SPI_TXFTLR 0x0014 +#define ROCKCHIP_SPI_RXFTLR 0x0018 +#define ROCKCHIP_SPI_TXFLR 0x001c +#define ROCKCHIP_SPI_RXFLR 0x0020 +#define ROCKCHIP_SPI_SR 0x0024 +#define ROCKCHIP_SPI_IPR 0x0028 +#define ROCKCHIP_SPI_IMR 0x002c +#define ROCKCHIP_SPI_ISR 0x0030 +#define ROCKCHIP_SPI_RISR 0x0034 +#define ROCKCHIP_SPI_ICR 0x0038 +#define ROCKCHIP_SPI_DMACR 0x003c +#define ROCKCHIP_SPI_DMATDLR 0x0040 +#define ROCKCHIP_SPI_DMARDLR 0x0044 +#define ROCKCHIP_SPI_TXDR 0x0400 +#define ROCKCHIP_SPI_RXDR 0x0800 + +/* Bit fields in CTRLR0 */ +#define CR0_DFS_OFFSET 0 + +#define CR0_CFS_OFFSET 2 + +#define CR0_SCPH_OFFSET 6 + +#define CR0_SCPOL_OFFSET 7 + +#define CR0_CSM_OFFSET 8 +#define CR0_CSM_KEEP 0x0 +/* ss_n be high for half sclk_out cycles */ +#define CR0_CSM_HALF 0X1 +/* ss_n be high for one sclk_out cycle */ +#define CR0_CSM_ONE 0x2 + +/* ss_n to sclk_out delay */ +#define CR0_SSD_OFFSET 10 +/* + * The period between ss_n active and + * sclk_out active is half sclk_out cycles + */ +#define CR0_SSD_HALF 0x0 +/* + * The period between ss_n active and + * sclk_out active is one sclk_out cycle + */ +#define CR0_SSD_ONE 0x1 + +#define CR0_EM_OFFSET 11 +#define CR0_EM_LITTLE 0x0 +#define CR0_EM_BIG 0x1 + +#define CR0_FBM_OFFSET 12 +#define CR0_FBM_MSB 0x0 +#define CR0_FBM_LSB 0x1 + +#define CR0_BHT_OFFSET 13 +#define CR0_BHT_16BIT 0x0 +#define CR0_BHT_8BIT 0x1 + +#define CR0_RSD_OFFSET 14 + +#define CR0_FRF_OFFSET 16 +#define CR0_FRF_SPI 0x0 +#define CR0_FRF_SSP 0x1 +#define CR0_FRF_MICROWIRE 0x2 + +#define CR0_XFM_OFFSET 18 +#define CR0_XFM_MASK (0x03 << SPI_XFM_OFFSET) +#define CR0_XFM_TR 0x0 +#define CR0_XFM_TO 0x1 +#define CR0_XFM_RO 0x2 + +#define CR0_OPM_OFFSET 20 +#define CR0_OPM_MASTER 0x0 +#define CR0_OPM_SLAVE 0x1 + +#define CR0_MTM_OFFSET 0x21 + +/* Bit fields in SER, 2bit */ +#define SER_MASK 0x3 + +/* Bit fields in SR, 5bit */ +#define SR_MASK 0x1f +#define SR_BUSY (1 << 0) +#define SR_TF_FULL (1 << 1) +#define SR_TF_EMPTY (1 << 2) +#define SR_RF_EMPTY (1 << 3) +#define SR_RF_FULL (1 << 4) + +/* Bit fields in ISR, IMR, ISR, RISR, 5bit */ +#define INT_MASK 0x1f +#define INT_TF_EMPTY (1 << 0) +#define INT_TF_OVERFLOW (1 << 1) +#define INT_RF_UNDERFLOW (1 << 2) +#define INT_RF_OVERFLOW (1 << 3) +#define INT_RF_FULL (1 << 4) + +/* Bit fields in ICR, 4bit */ +#define ICR_MASK 0x0f +#define ICR_ALL (1 << 0) +#define ICR_RF_UNDERFLOW (1 << 1) +#define ICR_RF_OVERFLOW (1 << 2) +#define ICR_TF_OVERFLOW (1 << 3) + +/* Bit fields in DMACR */ +#define RF_DMA_EN (1 << 0) +#define TF_DMA_EN (1 << 1) + +#define RXBUSY (1 << 0) +#define TXBUSY (1 << 1) + +enum rockchip_ssi_type { + SSI_MOTO_SPI = 0, + SSI_TI_SSP, + SSI_NS_MICROWIRE, +}; + +struct rockchip_spi_dma_data { + struct dma_chan *ch; + enum dma_transfer_direction direction; + dma_addr_t addr; +}; + +struct rockchip_spi { + struct device *dev; + struct spi_master *master; + + struct clk *spiclk; + struct clk *apb_pclk; + + void __iomem *regs; + /*depth of the FIFO buffer */ + u32 fifo_len; + /* max bus freq supported */ + u32 max_freq; + /* supported slave numbers */ + enum rockchip_ssi_type type; + + u16 mode; + u8 tmode; + u8 bpw; + u8 n_bytes; + unsigned len; + u32 speed; + + const void *tx; + const void *tx_end; + void *rx; + void *rx_end; + + u32 state; + /* protect state */ + spinlock_t lock; + + struct completion xfer_completion; + + u32 use_dma; + struct sg_table tx_sg; + struct sg_table rx_sg; + struct rockchip_spi_dma_data dma_rx; + struct rockchip_spi_dma_data dma_tx; +}; + +static inline void spi_enable_chip(struct rockchip_spi *rs, int enable) +{ + writel_relaxed((enable ? 1 : 0), rs->regs + ROCKCHIP_SPI_SSIENR); +} + +static inline void spi_set_clk(struct rockchip_spi *rs, u16 div) +{ + writel_relaxed(div, rs->regs + ROCKCHIP_SPI_BAUDR); +} + +static inline void flush_fifo(struct rockchip_spi *rs) +{ + while (readl_relaxed(rs->regs + ROCKCHIP_SPI_RXFLR)) + readl_relaxed(rs->regs + ROCKCHIP_SPI_RXDR); +} + +static inline void wait_for_idle(struct rockchip_spi *rs) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(5); + + do { + if (!(readl_relaxed(rs->regs + ROCKCHIP_SPI_SR) & SR_BUSY)) + return; + } while (time_before(jiffies, timeout)); + + dev_warn(rs->dev, "spi controller is in busy state!\n"); +} + +static u32 get_fifo_len(struct rockchip_spi *rs) +{ + u32 fifo; + + for (fifo = 2; fifo < 32; fifo++) { + writel_relaxed(fifo, rs->regs + ROCKCHIP_SPI_TXFTLR); + if (fifo != readl_relaxed(rs->regs + ROCKCHIP_SPI_TXFTLR)) + break; + } + + writel_relaxed(0, rs->regs + ROCKCHIP_SPI_TXFTLR); + + return (fifo == 31) ? 0 : fifo; +} + +static inline u32 tx_max(struct rockchip_spi *rs) +{ + u32 tx_left, tx_room; + + tx_left = (rs->tx_end - rs->tx) / rs->n_bytes; + tx_room = rs->fifo_len - readl_relaxed(rs->regs + ROCKCHIP_SPI_TXFLR); + + return min(tx_left, tx_room); +} + +static inline u32 rx_max(struct rockchip_spi *rs) +{ + u32 rx_left = (rs->rx_end - rs->rx) / rs->n_bytes; + u32 rx_room = (u32)readl_relaxed(rs->regs + ROCKCHIP_SPI_RXFLR); + + return min(rx_left, rx_room); +} + +static void rockchip_spi_set_cs(struct spi_device *spi, bool enable) +{ + u32 ser; + struct rockchip_spi *rs = spi_master_get_devdata(spi->master); + + ser = readl_relaxed(rs->regs + ROCKCHIP_SPI_SER) & SER_MASK; + + /* + * drivers/spi/spi.c: + * static void spi_set_cs(struct spi_device *spi, bool enable) + * { + * if (spi->mode & SPI_CS_HIGH) + * enable = !enable; + * + * if (spi->cs_gpio >= 0) + * gpio_set_value(spi->cs_gpio, !enable); + * else if (spi->master->set_cs) + * spi->master->set_cs(spi, !enable); + * } + * + * Note: enable(rockchip_spi_set_cs) = !enable(spi_set_cs) + */ + if (!enable) + ser |= 1 << spi->chip_select; + else + ser &= ~(1 << spi->chip_select); + + writel_relaxed(ser, rs->regs + ROCKCHIP_SPI_SER); +} + +static int rockchip_spi_prepare_message(struct spi_master *master, + struct spi_message *msg) +{ + struct rockchip_spi *rs = spi_master_get_devdata(master); + struct spi_device *spi = msg->spi; + + rs->mode = spi->mode; + + return 0; +} + +static int rockchip_spi_unprepare_message(struct spi_master *master, + struct spi_message *msg) +{ + unsigned long flags; + struct rockchip_spi *rs = spi_master_get_devdata(master); + + spin_lock_irqsave(&rs->lock, flags); + + /* + * For DMA mode, we need terminate DMA channel and flush + * fifo for the next transfer if DMA thansfer timeout. + * unprepare_message() was called by core if transfer complete + * or timeout. Maybe it is reasonable for error handling here. + */ + if (rs->use_dma) { + if (rs->state & RXBUSY) { + dmaengine_terminate_all(rs->dma_rx.ch); + flush_fifo(rs); + } + + if (rs->state & TXBUSY) + dmaengine_terminate_all(rs->dma_tx.ch); + } + + spin_unlock_irqrestore(&rs->lock, flags); + + return 0; +} + +static void rockchip_spi_pio_writer(struct rockchip_spi *rs) +{ + u32 max = tx_max(rs); + u32 txw = 0; + + while (max--) { + if (rs->n_bytes == 1) + txw = *(u8 *)(rs->tx); + else + txw = *(u16 *)(rs->tx); + + writel_relaxed(txw, rs->regs + ROCKCHIP_SPI_TXDR); + rs->tx += rs->n_bytes; + } +} + +static void rockchip_spi_pio_reader(struct rockchip_spi *rs) +{ + u32 max = rx_max(rs); + u32 rxw; + + while (max--) { + rxw = readl_relaxed(rs->regs + ROCKCHIP_SPI_RXDR); + if (rs->n_bytes == 1) + *(u8 *)(rs->rx) = (u8)rxw; + else + *(u16 *)(rs->rx) = (u16)rxw; + rs->rx += rs->n_bytes; + } +} + +static int rockchip_spi_pio_transfer(struct rockchip_spi *rs) +{ + int remain = 0; + + do { + if (rs->tx) { + remain = rs->tx_end - rs->tx; + rockchip_spi_pio_writer(rs); + } + + if (rs->rx) { + remain = rs->rx_end - rs->rx; + rockchip_spi_pio_reader(rs); + } + + cpu_relax(); + } while (remain); + + /* If tx, wait until the FIFO data completely. */ + if (rs->tx) + wait_for_idle(rs); + + return 0; +} + +static void rockchip_spi_dma_rxcb(void *data) +{ + unsigned long flags; + struct rockchip_spi *rs = data; + + spin_lock_irqsave(&rs->lock, flags); + + rs->state &= ~RXBUSY; + if (!(rs->state & TXBUSY)) + spi_finalize_current_transfer(rs->master); + + spin_unlock_irqrestore(&rs->lock, flags); +} + +static void rockchip_spi_dma_txcb(void *data) +{ + unsigned long flags; + struct rockchip_spi *rs = data; + + /* Wait until the FIFO data completely. */ + wait_for_idle(rs); + + spin_lock_irqsave(&rs->lock, flags); + + rs->state &= ~TXBUSY; + if (!(rs->state & RXBUSY)) + spi_finalize_current_transfer(rs->master); + + spin_unlock_irqrestore(&rs->lock, flags); +} + +static int rockchip_spi_dma_transfer(struct rockchip_spi *rs) +{ + unsigned long flags; + struct dma_slave_config rxconf, txconf; + struct dma_async_tx_descriptor *rxdesc, *txdesc; + + spin_lock_irqsave(&rs->lock, flags); + rs->state &= ~RXBUSY; + rs->state &= ~TXBUSY; + spin_unlock_irqrestore(&rs->lock, flags); + + if (rs->rx) { + rxconf.direction = rs->dma_rx.direction; + rxconf.src_addr = rs->dma_rx.addr; + rxconf.src_addr_width = rs->n_bytes; + rxconf.src_maxburst = rs->n_bytes; + dmaengine_slave_config(rs->dma_rx.ch, &rxconf); + + rxdesc = dmaengine_prep_slave_sg( + rs->dma_rx.ch, + rs->rx_sg.sgl, rs->rx_sg.nents, + rs->dma_rx.direction, DMA_PREP_INTERRUPT); + + rxdesc->callback = rockchip_spi_dma_rxcb; + rxdesc->callback_param = rs; + } + + if (rs->tx) { + txconf.direction = rs->dma_tx.direction; + txconf.dst_addr = rs->dma_tx.addr; + txconf.dst_addr_width = rs->n_bytes; + txconf.dst_maxburst = rs->n_bytes; + dmaengine_slave_config(rs->dma_tx.ch, &txconf); + + txdesc = dmaengine_prep_slave_sg( + rs->dma_tx.ch, + rs->tx_sg.sgl, rs->tx_sg.nents, + rs->dma_tx.direction, DMA_PREP_INTERRUPT); + + txdesc->callback = rockchip_spi_dma_txcb; + txdesc->callback_param = rs; + } + + /* rx must be started before tx due to spi instinct */ + if (rs->rx) { + spin_lock_irqsave(&rs->lock, flags); + rs->state |= RXBUSY; + spin_unlock_irqrestore(&rs->lock, flags); + dmaengine_submit(rxdesc); + dma_async_issue_pending(rs->dma_rx.ch); + } + + if (rs->tx) { + spin_lock_irqsave(&rs->lock, flags); + rs->state |= TXBUSY; + spin_unlock_irqrestore(&rs->lock, flags); + dmaengine_submit(txdesc); + dma_async_issue_pending(rs->dma_tx.ch); + } + + return 1; +} + +static void rockchip_spi_config(struct rockchip_spi *rs) +{ + u32 div = 0; + u32 dmacr = 0; + + u32 cr0 = (CR0_BHT_8BIT << CR0_BHT_OFFSET) + | (CR0_SSD_ONE << CR0_SSD_OFFSET); + + cr0 |= (rs->n_bytes << CR0_DFS_OFFSET); + cr0 |= ((rs->mode & 0x3) << CR0_SCPH_OFFSET); + cr0 |= (rs->tmode << CR0_XFM_OFFSET); + cr0 |= (rs->type << CR0_FRF_OFFSET); + + if (rs->use_dma) { + if (rs->tx) + dmacr |= TF_DMA_EN; + if (rs->rx) + dmacr |= RF_DMA_EN; + } + + /* div doesn't support odd number */ + div = rs->max_freq / rs->speed; + div = (div + 1) & 0xfffe; + + spi_enable_chip(rs, 0); + + writel_relaxed(cr0, rs->regs + ROCKCHIP_SPI_CTRLR0); + + writel_relaxed(rs->len - 1, rs->regs + ROCKCHIP_SPI_CTRLR1); + writel_relaxed(rs->fifo_len / 2 - 1, rs->regs + ROCKCHIP_SPI_TXFTLR); + writel_relaxed(rs->fifo_len / 2 - 1, rs->regs + ROCKCHIP_SPI_RXFTLR); + + writel_relaxed(0, rs->regs + ROCKCHIP_SPI_DMATDLR); + writel_relaxed(0, rs->regs + ROCKCHIP_SPI_DMARDLR); + writel_relaxed(dmacr, rs->regs + ROCKCHIP_SPI_DMACR); + + spi_set_clk(rs, div); + + dev_dbg(rs->dev, "cr0 0x%x, div %d\n", cr0, div); + + spi_enable_chip(rs, 1); +} + +static int rockchip_spi_transfer_one( + struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + int ret = 0; + struct rockchip_spi *rs = spi_master_get_devdata(master); + + WARN_ON((readl_relaxed(rs->regs + ROCKCHIP_SPI_SR) & SR_BUSY)); + + if (!xfer->tx_buf && !xfer->rx_buf) { + dev_err(rs->dev, "No buffer for transfer\n"); + return -EINVAL; + } + + rs->speed = xfer->speed_hz; + rs->bpw = xfer->bits_per_word; + rs->n_bytes = rs->bpw >> 3; + + rs->tx = xfer->tx_buf; + rs->tx_end = rs->tx + xfer->len; + rs->rx = xfer->rx_buf; + rs->rx_end = rs->rx + xfer->len; + rs->len = xfer->len; + + rs->tx_sg = xfer->tx_sg; + rs->rx_sg = xfer->rx_sg; + + if (rs->tx && rs->rx) + rs->tmode = CR0_XFM_TR; + else if (rs->tx) + rs->tmode = CR0_XFM_TO; + else if (rs->rx) + rs->tmode = CR0_XFM_RO; + + if (master->can_dma && master->can_dma(master, spi, xfer)) + rs->use_dma = 1; + else + rs->use_dma = 0; + + rockchip_spi_config(rs); + + if (rs->use_dma) + ret = rockchip_spi_dma_transfer(rs); + else + ret = rockchip_spi_pio_transfer(rs); + + return ret; +} + +static bool rockchip_spi_can_dma(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct rockchip_spi *rs = spi_master_get_devdata(master); + + return (xfer->len > rs->fifo_len); +} + +static int rockchip_spi_probe(struct platform_device *pdev) +{ + int ret = 0; + struct rockchip_spi *rs; + struct spi_master *master; + struct resource *mem; + + master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi)); + if (!master) + return -ENOMEM; + + platform_set_drvdata(pdev, master); + + rs = spi_master_get_devdata(master); + memset(rs, 0, sizeof(struct rockchip_spi)); + + /* Get basic io resource and map it */ + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + rs->regs = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(rs->regs)) { + ret = PTR_ERR(rs->regs); + goto err_ioremap_resource; + } + + rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk"); + if (IS_ERR(rs->apb_pclk)) { + dev_err(&pdev->dev, "Failed to get apb_pclk\n"); + ret = PTR_ERR(rs->apb_pclk); + goto err_ioremap_resource; + } + + rs->spiclk = devm_clk_get(&pdev->dev, "spiclk"); + if (IS_ERR(rs->spiclk)) { + dev_err(&pdev->dev, "Failed to get spi_pclk\n"); + ret = PTR_ERR(rs->spiclk); + goto err_ioremap_resource; + } + + ret = clk_prepare_enable(rs->apb_pclk); + if (ret) { + dev_err(&pdev->dev, "Failed to enable apb_pclk\n"); + goto err_ioremap_resource; + } + + ret = clk_prepare_enable(rs->spiclk); + if (ret) { + dev_err(&pdev->dev, "Failed to enable spi_clk\n"); + goto err_spiclk_enable; + } + + spi_enable_chip(rs, 0); + + rs->type = SSI_MOTO_SPI; + rs->master = master; + rs->dev = &pdev->dev; + rs->max_freq = clk_get_rate(rs->spiclk); + + rs->fifo_len = get_fifo_len(rs); + if (!rs->fifo_len) { + dev_err(&pdev->dev, "Failed to get fifo length\n"); + ret = -EINVAL; + goto err_get_fifo_len; + } + + spin_lock_init(&rs->lock); + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + master->auto_runtime_pm = true; + master->bus_num = pdev->id; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP; + master->num_chipselect = 2; + master->dev.of_node = pdev->dev.of_node; + master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8); + + master->set_cs = rockchip_spi_set_cs; + master->prepare_message = rockchip_spi_prepare_message; + master->unprepare_message = rockchip_spi_unprepare_message; + master->transfer_one = rockchip_spi_transfer_one; + + rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx"); + if (!rs->dma_tx.ch) + dev_warn(rs->dev, "Failed to request TX DMA channel\n"); + + rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx"); + if (!rs->dma_rx.ch) { + if (rs->dma_tx.ch) { + dma_release_channel(rs->dma_tx.ch); + rs->dma_tx.ch = NULL; + } + dev_warn(rs->dev, "Failed to request RX DMA channel\n"); + } + + if (rs->dma_tx.ch && rs->dma_rx.ch) { + rs->dma_tx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_TXDR); + rs->dma_rx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_RXDR); + rs->dma_tx.direction = DMA_MEM_TO_DEV; + rs->dma_tx.direction = DMA_DEV_TO_MEM; + + master->can_dma = rockchip_spi_can_dma; + master->dma_tx = rs->dma_tx.ch; + master->dma_rx = rs->dma_rx.ch; + } + + ret = devm_spi_register_master(&pdev->dev, master); + if (ret) { + dev_err(&pdev->dev, "Failed to register master\n"); + goto err_register_master; + } + + return 0; + +err_register_master: + if (rs->dma_tx.ch) + dma_release_channel(rs->dma_tx.ch); + if (rs->dma_rx.ch) + dma_release_channel(rs->dma_rx.ch); +err_get_fifo_len: + clk_disable_unprepare(rs->spiclk); +err_spiclk_enable: + clk_disable_unprepare(rs->apb_pclk); +err_ioremap_resource: + spi_master_put(master); + + return ret; +} + +static int rockchip_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); + struct rockchip_spi *rs = spi_master_get_devdata(master); + + pm_runtime_disable(&pdev->dev); + + clk_disable_unprepare(rs->spiclk); + clk_disable_unprepare(rs->apb_pclk); + + if (rs->dma_tx.ch) + dma_release_channel(rs->dma_tx.ch); + if (rs->dma_rx.ch) + dma_release_channel(rs->dma_rx.ch); + + spi_master_put(master); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int rockchip_spi_suspend(struct device *dev) +{ + int ret = 0; + struct spi_master *master = dev_get_drvdata(dev); + struct rockchip_spi *rs = spi_master_get_devdata(master); + + ret = spi_master_suspend(rs->master); + if (ret) + return ret; + + if (!pm_runtime_suspended(dev)) { + clk_disable_unprepare(rs->spiclk); + clk_disable_unprepare(rs->apb_pclk); + } + + return ret; +} + +static int rockchip_spi_resume(struct device *dev) +{ + int ret = 0; + struct spi_master *master = dev_get_drvdata(dev); + struct rockchip_spi *rs = spi_master_get_devdata(master); + + if (!pm_runtime_suspended(dev)) { + ret = clk_prepare_enable(rs->apb_pclk); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(rs->spiclk); + if (ret < 0) { + clk_disable_unprepare(rs->apb_pclk); + return ret; + } + } + + ret = spi_master_resume(rs->master); + if (ret < 0) { + clk_disable_unprepare(rs->spiclk); + clk_disable_unprepare(rs->apb_pclk); + } + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int rockchip_spi_runtime_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct rockchip_spi *rs = spi_master_get_devdata(master); + + clk_disable_unprepare(rs->spiclk); + clk_disable_unprepare(rs->apb_pclk); + + return 0; +} + +static int rockchip_spi_runtime_resume(struct device *dev) +{ + int ret; + struct spi_master *master = dev_get_drvdata(dev); + struct rockchip_spi *rs = spi_master_get_devdata(master); + + ret = clk_prepare_enable(rs->apb_pclk); + if (ret) + return ret; + + ret = clk_prepare_enable(rs->spiclk); + if (ret) + clk_disable_unprepare(rs->apb_pclk); + + return ret; +} +#endif /* CONFIG_PM_RUNTIME */ + +static const struct dev_pm_ops rockchip_spi_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rockchip_spi_suspend, rockchip_spi_resume) + SET_RUNTIME_PM_OPS(rockchip_spi_runtime_suspend, + rockchip_spi_runtime_resume, NULL) +}; + +static const struct of_device_id rockchip_spi_dt_match[] = { + { .compatible = "rockchip,rk3066-spi", }, + { .compatible = "rockchip,rk3188-spi", }, + { .compatible = "rockchip,rk3288-spi", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rockchip_spi_dt_match); + +static struct platform_driver rockchip_spi_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .pm = &rockchip_spi_pm, + .of_match_table = of_match_ptr(rockchip_spi_dt_match), + }, + .probe = rockchip_spi_probe, + .remove = rockchip_spi_remove, +}; + +module_platform_driver(rockchip_spi_driver); + +MODULE_AUTHOR("Addy Ke <addy.ke@rock-chips.com>"); +MODULE_DESCRIPTION("ROCKCHIP SPI Controller Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c index 10112745bb17..c850dfdfa9e3 100644 --- a/drivers/spi/spi-rspi.c +++ b/drivers/spi/spi-rspi.c @@ -477,7 +477,7 @@ static int rspi_dma_transfer(struct rspi_data *rspi, struct sg_table *tx, tx->sgl, tx->nents, DMA_TO_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc_tx) - return -EIO; + goto no_dma; irq_mask |= SPCR_SPTIE; } @@ -486,7 +486,7 @@ static int rspi_dma_transfer(struct rspi_data *rspi, struct sg_table *tx, rx->sgl, rx->nents, DMA_FROM_DEVICE, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); if (!desc_rx) - return -EIO; + goto no_dma; irq_mask |= SPCR_SPRIE; } @@ -540,6 +540,12 @@ static int rspi_dma_transfer(struct rspi_data *rspi, struct sg_table *tx, enable_irq(rspi->rx_irq); return ret; + +no_dma: + pr_warn_once("%s %s: DMA not available, falling back to PIO\n", + dev_driver_string(&rspi->master->dev), + dev_name(&rspi->master->dev)); + return -EAGAIN; } static void rspi_receive_init(const struct rspi_data *rspi) @@ -593,8 +599,10 @@ static int rspi_common_transfer(struct rspi_data *rspi, if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) { /* rx_buf can be NULL on RSPI on SH in TX-only Mode */ - return rspi_dma_transfer(rspi, &xfer->tx_sg, - xfer->rx_buf ? &xfer->rx_sg : NULL); + ret = rspi_dma_transfer(rspi, &xfer->tx_sg, + xfer->rx_buf ? &xfer->rx_sg : NULL); + if (ret != -EAGAIN) + return ret; } ret = rspi_pio_transfer(rspi, xfer->tx_buf, xfer->rx_buf, xfer->len); @@ -630,7 +638,6 @@ static int rspi_rz_transfer_one(struct spi_master *master, struct spi_transfer *xfer) { struct rspi_data *rspi = spi_master_get_devdata(master); - int ret; rspi_rz_receive_init(rspi); @@ -649,8 +656,11 @@ static int qspi_transfer_out(struct rspi_data *rspi, struct spi_transfer *xfer) { int ret; - if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) - return rspi_dma_transfer(rspi, &xfer->tx_sg, NULL); + if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) { + ret = rspi_dma_transfer(rspi, &xfer->tx_sg, NULL); + if (ret != -EAGAIN) + return ret; + } ret = rspi_pio_transfer(rspi, xfer->tx_buf, NULL, xfer->len); if (ret < 0) @@ -664,8 +674,11 @@ static int qspi_transfer_out(struct rspi_data *rspi, struct spi_transfer *xfer) static int qspi_transfer_in(struct rspi_data *rspi, struct spi_transfer *xfer) { - if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) - return rspi_dma_transfer(rspi, NULL, &xfer->rx_sg); + if (rspi->master->can_dma && __rspi_can_dma(rspi, xfer)) { + int ret = rspi_dma_transfer(rspi, NULL, &xfer->rx_sg); + if (ret != -EAGAIN) + return ret; + } return rspi_pio_transfer(rspi, NULL, xfer->rx_buf, xfer->len); } @@ -927,19 +940,19 @@ static int rspi_request_dma(struct device *dev, struct spi_master *master, return 0; } -static void rspi_release_dma(struct rspi_data *rspi) +static void rspi_release_dma(struct spi_master *master) { - if (rspi->master->dma_tx) - dma_release_channel(rspi->master->dma_tx); - if (rspi->master->dma_rx) - dma_release_channel(rspi->master->dma_rx); + if (master->dma_tx) + dma_release_channel(master->dma_tx); + if (master->dma_rx) + dma_release_channel(master->dma_rx); } static int rspi_remove(struct platform_device *pdev) { struct rspi_data *rspi = platform_get_drvdata(pdev); - rspi_release_dma(rspi); + rspi_release_dma(rspi->master); pm_runtime_disable(&pdev->dev); return 0; @@ -1141,7 +1154,7 @@ static int rspi_probe(struct platform_device *pdev) return 0; error3: - rspi_release_dma(rspi); + rspi_release_dma(master); error2: pm_runtime_disable(&pdev->dev); error1: |