From: Jassi Brar <jaswinder.singh@xxxxxxxxxx> This patch adds support for controller found on synquacer platforms. Signed-off-by: Jassi Brar <jaswinder.singh@xxxxxxxxxx> --- drivers/spi/Kconfig | 11 + drivers/spi/Makefile | 1 + drivers/spi/spi-synquacer.c | 661 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 673 insertions(+) create mode 100644 drivers/spi/spi-synquacer.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 6037839..9e04bbe 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -659,6 +659,17 @@ config SPI_SUN6I help This enables using the SPI controller on the Allwinner A31 SoCs. +config SPI_SYNQUACER + tristate "Socionext's Synquacer HighSpeed SPI controller" + depends on ARCH_SYNQUACER || COMPILE_TEST + select SPI_BITBANG + help + SPI driver for Socionext's High speed SPI controller which provides + various operating modes for interfacing to serial peripheral devices + that use the de-facto standard SPI protocol. + + It also supports the new dual-bit and quad-bit SPI protocol. + config SPI_MXS tristate "Freescale MXS SPI controller" depends on ARCH_MXS diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 34c5f28..7c222f2 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -96,6 +96,7 @@ obj-$(CONFIG_SPI_STM32) += spi-stm32.o obj-$(CONFIG_SPI_ST_SSC4) += spi-st-ssc4.o obj-$(CONFIG_SPI_SUN4I) += spi-sun4i.o obj-$(CONFIG_SPI_SUN6I) += spi-sun6i.o +obj-$(CONFIG_SPI_SYNQUACER) += spi-synquacer.o obj-$(CONFIG_SPI_TEGRA114) += spi-tegra114.o obj-$(CONFIG_SPI_TEGRA20_SFLASH) += spi-tegra20-sflash.o obj-$(CONFIG_SPI_TEGRA20_SLINK) += spi-tegra20-slink.o diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c new file mode 100644 index 0000000..15568b1 --- /dev/null +++ b/drivers/spi/spi-synquacer.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Synquacer HSSPI controller driver +// +// Copyright (c) 2015-2018 Socionext Inc. +// Copyright (c) 2018 Linaro Ltd. +// + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/spinlock.h> +#include <linux/clk.h> +#include <linux/clk-provider.h> + +#define MCTRL 0x0 +#define MEN BIT(0) +#define CSEN BIT(1) +#define BPCLK BIT(3) +#define MES BIT(4) +#define SYNCON BIT(5) + +#define PCC0 0x4 +#define PCC(n) (PCC0 + (n) * 4) +#define RTM BIT(3) +#define ACES BIT(2) +#define SAFESYNC BIT(16) +#define CPHA BIT(0) +#define CPOL BIT(1) +#define SSPOL BIT(4) +#define SDIR BIT(7) +#define SS2CD 5 +#define SENDIAN BIT(8) +#define CDRS_SHIFT 9 +#define CDRS_MASK 0x7f + +#define TXF 0x14 +#define TXE 0x18 +#define TXC 0x1c +#define RXF 0x20 +#define RXE 0x24 +#define RXC 0x28 + +#define FAULTF 0x2c +#define FAULTC 0x30 + +#define DMCFG 0x34 +#define SSDC BIT(1) +#define MSTARTEN BIT(2) + +#define DMSTART 0x38 +#define TRIGGER BIT(0) +#define DMSTOP BIT(8) +#define CS_MASK 3 +#define CS_SHIFT 16 +#define DATA_TXRX 0 +#define DATA_RX 1 +#define DATA_TX 2 +#define DATA_MASK 3 +#define DATA_SHIFT 26 +#define BUS_WIDTH 24 + +#define DMBCC 0x3c +#define DMSTATUS 0x40 +#define RX_DATA_MASK 0x1f +#define RX_DATA_SHIFT 8 +#define TX_DATA_MASK 0x1f +#define TX_DATA_SHIFT 16 + +#define TXBITCNT 0x44 + +#define FIFOCFG 0x4c +#define BPW_MASK 0x3 +#define BPW_SHIFT 8 +#define RX_FLUSH BIT(11) +#define TX_FLUSH BIT(12) +#define RX_TRSHLD_MASK 0xf +#define RX_TRSHLD_SHIFT 0 +#define TX_TRSHLD_MASK 0xf +#define TX_TRSHLD_SHIFT 4 + +#define TXFIFO 0x50 +#define RXFIFO 0x90 +#define MID 0xfc + +#define FIFO_DEPTH 16 +#define TX_TRSHLD 4 +#define RX_TRSHLD (FIFO_DEPTH - TX_TRSHLD) + +#define TXBIT BIT(1) +#define RXBIT BIT(2) + +#define IHCLK 0 +#define IPCLK 1 + +struct synquacer_spi { + struct device *dev; + struct spi_master *master; + + unsigned int cs; + unsigned int bpw; + unsigned int mode; + unsigned int speed; + bool aces, rtm; + void *rx_buf; + const void *tx_buf; + struct clk *clk[2]; + void __iomem *regs; + unsigned int tx_words, rx_words; + unsigned int bus_width; +}; + +static void read_fifo(struct synquacer_spi *sspi) +{ + u32 len = readl_relaxed(sspi->regs + DMSTATUS); + int i; + + len = (len >> RX_DATA_SHIFT) & RX_DATA_MASK; + len = min_t(unsigned int, len, sspi->rx_words); + + switch (sspi->bpw) { + case 8: + { + u8 *buf = sspi->rx_buf; + + for (i = 0; i < len; i++) + *buf++ = readb_relaxed(sspi->regs + RXFIFO); + sspi->rx_buf = buf; + break; + } + case 16: + { + u16 *buf = sspi->rx_buf; + + for (i = 0; i < len; i++) + *buf++ = readw_relaxed(sspi->regs + RXFIFO); + sspi->rx_buf = buf; + break; + } + default: + { + u32 *buf = sspi->rx_buf; + + for (i = 0; i < len; i++) + *buf++ = readl_relaxed(sspi->regs + RXFIFO); + sspi->rx_buf = buf; + break; + } + } + + sspi->rx_words -= len; +} + +static void write_fifo(struct synquacer_spi *sspi) +{ + u32 len = readl_relaxed(sspi->regs + DMSTATUS); + int i; + + len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK; + len = min_t(unsigned int, FIFO_DEPTH - len, sspi->tx_words); + + switch (sspi->bpw) { + case 8: + { + const u8 *buf = sspi->tx_buf; + + for (i = 0; i < len; i++) + writeb_relaxed(*buf++, sspi->regs + TXFIFO); + sspi->tx_buf = buf; + break; + } + case 16: + { + const u16 *buf = sspi->tx_buf; + + for (i = 0; i < len; i++) + writew_relaxed(*buf++, sspi->regs + TXFIFO); + sspi->tx_buf = buf; + break; + } + default: + { + const u32 *buf = sspi->tx_buf; + + for (i = 0; i < len; i++) + writel_relaxed(*buf++, sspi->regs + TXFIFO); + sspi->tx_buf = buf; + break; + } + } + sspi->tx_words -= len; +} + +static int synquacer_spi_config(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct synquacer_spi *sspi = spi_master_get_devdata(master); + unsigned int speed, mode, bpw, cs, bus_width; + unsigned long rate; + u32 val, div; + + /* Full Duplex only on 1bit wide bus */ + if (xfer->rx_buf && xfer->tx_buf && + (xfer->rx_nbits != 1 || xfer->tx_nbits != 1)) { + dev_err(sspi->dev, + "RX and TX bus widths must match for Full-Duplex!\n"); + return -EINVAL; + } + + if (xfer->tx_buf) + bus_width = xfer->tx_nbits; + else + bus_width = xfer->rx_nbits; + + mode = spi->mode; + cs = spi->chip_select; + speed = xfer->speed_hz; + bpw = xfer->bits_per_word; + + /* return if nothing to change */ + if (speed == sspi->speed && + bus_width == sspi->bus_width && bpw == sspi->bpw && + mode == sspi->mode && cs == sspi->cs) { + return 0; + } + + rate = master->max_speed_hz; + + div = DIV_ROUND_UP(rate, speed); + if (div > 254) { + dev_err(sspi->dev, "Requested rate too low (%u)\n", + sspi->speed); + return -EINVAL; + } + + val = readl_relaxed(sspi->regs + PCC(cs)); + val &= ~SAFESYNC; + if (bpw == 8 && (mode & (SPI_TX_DUAL | SPI_RX_DUAL)) && div < 3) + val |= SAFESYNC; + if (bpw == 8 && (mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 6) + val |= SAFESYNC; + if (bpw == 16 && (mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 3) + val |= SAFESYNC; + + if (mode & SPI_CPHA) + val |= CPHA; + else + val &= ~CPHA; + + if (mode & SPI_CPOL) + val |= CPOL; + else + val &= ~CPOL; + + if (mode & SPI_CS_HIGH) + val |= SSPOL; + else + val &= ~SSPOL; + + if (mode & SPI_LSB_FIRST) + val |= SDIR; + else + val &= ~SDIR; + + if (sspi->aces) + val |= ACES; + else + val &= ~ACES; + + if (sspi->rtm) + val |= RTM; + else + val &= ~RTM; + + val |= (3 << SS2CD); + val |= SENDIAN; + + val &= ~(CDRS_MASK << CDRS_SHIFT); + val |= ((div >> 1) << CDRS_SHIFT); + + writel_relaxed(val, sspi->regs + PCC(cs)); + + val = readl_relaxed(sspi->regs + FIFOCFG); + val &= ~(BPW_MASK << BPW_SHIFT); + val |= ((bpw / 8 - 1) << BPW_SHIFT); + writel_relaxed(val, sspi->regs + FIFOCFG); + + val = readl_relaxed(sspi->regs + DMSTART); + val &= ~(DATA_MASK << DATA_SHIFT); + + if (xfer->tx_buf && xfer->rx_buf) + val |= (DATA_TXRX << DATA_SHIFT); + else if (xfer->rx_buf) + val |= (DATA_RX << DATA_SHIFT); + else + val |= (DATA_TX << DATA_SHIFT); + + val &= ~(3 << BUS_WIDTH); + val |= ((bus_width >> 1) << BUS_WIDTH); + writel_relaxed(val, sspi->regs + DMSTART); + + sspi->bpw = bpw; + sspi->mode = mode; + sspi->speed = speed; + sspi->cs = spi->chip_select; + sspi->bus_width = bus_width; + + return 0; +} + +static int synquacer_spi_transfer_one(struct spi_master *master, + struct spi_device *spi, + struct spi_transfer *xfer) +{ + struct synquacer_spi *sspi = spi_master_get_devdata(master); + int ret, words, busy = 0; + unsigned long bpw; + u32 val; + + val = readl_relaxed(sspi->regs + FIFOCFG); + val |= RX_FLUSH; + val |= TX_FLUSH; + writel_relaxed(val, sspi->regs + FIFOCFG); + + /* See if we can transfer 4-bytes as 1 word even if not asked */ + bpw = xfer->bits_per_word; + if (bpw == 8 && !(xfer->len % 4) && !(spi->mode & SPI_LSB_FIRST)) + xfer->bits_per_word = 32; + + ret = synquacer_spi_config(master, spi, xfer); + + /* restore */ + xfer->bits_per_word = bpw; + + if (ret) + return ret; + + sspi->tx_buf = xfer->tx_buf; + sspi->rx_buf = xfer->rx_buf; + + switch (sspi->bpw) { + case 8: + words = xfer->len; + break; + case 16: + words = xfer->len / 2; + break; + default: + words = xfer->len / 4; + break; + } + + if (xfer->tx_buf) { + busy |= TXBIT; + sspi->tx_words = words; + } else { + sspi->tx_words = 0; + } + + if (xfer->rx_buf) { + busy |= RXBIT; + sspi->rx_words = words; + } else { + sspi->rx_words = 0; + } + + if (xfer->tx_buf) + write_fifo(sspi); + + if (xfer->rx_buf) { + val = readl_relaxed(sspi->regs + FIFOCFG); + val &= ~(RX_TRSHLD_MASK << RX_TRSHLD_SHIFT); + val |= ((sspi->rx_words > FIFO_DEPTH ? + RX_TRSHLD : sspi->rx_words) << RX_TRSHLD_SHIFT); + writel_relaxed(val, sspi->regs + FIFOCFG); + } + + writel_relaxed(~0, sspi->regs + TXC); + writel_relaxed(~0, sspi->regs + RXC); + + /* Trigger */ + val = readl_relaxed(sspi->regs + DMSTART); + val |= TRIGGER; + writel_relaxed(val, sspi->regs + DMSTART); + + while (busy & (RXBIT | TXBIT)) { + if (sspi->rx_words) + read_fifo(sspi); + else + busy &= ~RXBIT; + + if (sspi->tx_words) { + write_fifo(sspi); + } else { + u32 len; + + do { /* wait for shifter to empty out */ + cpu_relax(); + len = readl_relaxed(sspi->regs + DMSTATUS); + len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK; + } while (xfer->tx_buf && len); + busy &= ~TXBIT; + } + } + + return 0; +} + +static void synquacer_spi_set_cs(struct spi_device *spi, bool enable) +{ + struct synquacer_spi *sspi = spi_master_get_devdata(spi->master); + u32 val; + + val = readl_relaxed(sspi->regs + DMSTART); + val &= ~(CS_MASK << CS_SHIFT); + val |= spi->chip_select << CS_SHIFT; + + if (!enable) { + writel_relaxed(val, sspi->regs + DMSTART); + + val = readl_relaxed(sspi->regs + DMSTART); + val &= ~DMSTOP; + writel_relaxed(val, sspi->regs + DMSTART); + } else { + val |= DMSTOP; + writel_relaxed(val, sspi->regs + DMSTART); + + if (sspi->rx_buf) { + u32 buf[16]; + + sspi->rx_buf = buf; + sspi->rx_words = 16; + read_fifo(sspi); + } + } +} + +static int synquacer_spi_enable(struct spi_master *master) +{ + struct synquacer_spi *sspi = spi_master_get_devdata(master); + u32 val; + + /* Disable module */ + writel_relaxed(0, sspi->regs + MCTRL); + val = 0xfffff; + while (--val && (readl_relaxed(sspi->regs + MCTRL) & MES)) + cpu_relax(); + if (!val) + return -EBUSY; + + writel_relaxed(0, sspi->regs + TXE); + writel_relaxed(0, sspi->regs + RXE); + val = readl_relaxed(sspi->regs + TXF); + writel_relaxed(val, sspi->regs + TXC); + val = readl_relaxed(sspi->regs + RXF); + writel_relaxed(val, sspi->regs + RXC); + val = readl_relaxed(sspi->regs + FAULTF); + writel_relaxed(val, sspi->regs + FAULTC); + + val = readl_relaxed(sspi->regs + DMCFG); + val &= ~SSDC; + val &= ~MSTARTEN; + writel_relaxed(val, sspi->regs + DMCFG); + + val = readl_relaxed(sspi->regs + MCTRL); + if (IS_ERR(sspi->clk[IPCLK])) + val &= ~BPCLK; + else + val |= BPCLK; + + val &= ~CSEN; + val |= MEN; + val |= SYNCON; + writel_relaxed(val, sspi->regs + MCTRL); + + return 0; +} + +static int synquacer_spi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct spi_master *master; + struct synquacer_spi *sspi; + struct resource *res; + int ret; + + master = spi_alloc_master(&pdev->dev, sizeof(*sspi)); + if (!master) + return -ENOMEM; + platform_set_drvdata(pdev, master); + + sspi = spi_master_get_devdata(master); + sspi->dev = &pdev->dev; + sspi->master = master; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sspi->regs = devm_ioremap_resource(sspi->dev, res); + if (IS_ERR(sspi->regs)) { + ret = PTR_ERR(sspi->regs); + goto put_spi; + } + + sspi->clk[IHCLK] = devm_clk_get(sspi->dev, "iHCLK"); + if (IS_ERR(sspi->clk[IHCLK])) { + dev_err(&pdev->dev, "iHCLK not found\n"); + ret = PTR_ERR(sspi->clk[IHCLK]); + goto put_spi; + } + + sspi->clk[IPCLK] = devm_clk_get(sspi->dev, "iPCLK"); + + sspi->aces = of_property_read_bool(np, "socionext,set-aces"); + sspi->rtm = of_property_read_bool(np, "socionext,use-rtm"); + + master->num_chipselect = 4; /* max 4 supported */ + + clk_prepare_enable(sspi->clk[IPCLK]); + ret = clk_prepare_enable(sspi->clk[IHCLK]); + if (ret) + goto put_spi; + + master->dev.of_node = np; + master->auto_runtime_pm = true; + master->bus_num = pdev->id; + + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_DUAL | SPI_RX_DUAL | + SPI_TX_QUAD | SPI_RX_QUAD; + master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(24) + | SPI_BPW_MASK(16) | SPI_BPW_MASK(8); + + if (!IS_ERR(sspi->clk[IPCLK])) + master->max_speed_hz = clk_get_rate(sspi->clk[IPCLK]); + else + master->max_speed_hz = clk_get_rate(sspi->clk[IHCLK]); + master->min_speed_hz = master->max_speed_hz / 254; + + master->set_cs = synquacer_spi_set_cs; + master->transfer_one = synquacer_spi_transfer_one; + + ret = synquacer_spi_enable(master); + if (ret) + goto fail_enable; + + pm_runtime_set_active(sspi->dev); + pm_runtime_enable(sspi->dev); + + ret = devm_spi_register_master(sspi->dev, master); + if (ret) + goto disable_pm; + + return 0; + +disable_pm: + pm_runtime_disable(sspi->dev); +fail_enable: + clk_disable_unprepare(sspi->clk[IHCLK]); + clk_disable_unprepare(sspi->clk[IPCLK]); +put_spi: + spi_master_put(master); + + return ret; +} + +static int synquacer_spi_remove(struct platform_device *pdev) +{ + struct spi_master *master = platform_get_drvdata(pdev); + struct synquacer_spi *sspi = spi_master_get_devdata(master); + + pm_runtime_disable(sspi->dev); + clk_disable_unprepare(sspi->clk[IHCLK]); + clk_disable_unprepare(sspi->clk[IPCLK]); + spi_master_put(master); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int synquacer_spi_suspend(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct synquacer_spi *sspi = spi_master_get_devdata(master); + int ret; + + ret = spi_master_suspend(master); + if (ret) + return ret; + + if (!pm_runtime_suspended(dev)) { + clk_disable_unprepare(sspi->clk[IPCLK]); + clk_disable_unprepare(sspi->clk[IHCLK]); + } + + return ret; +} + +static int synquacer_spi_resume(struct device *dev) +{ + struct spi_master *master = dev_get_drvdata(dev); + struct synquacer_spi *sspi = spi_master_get_devdata(master); + int ret; + + if (!pm_runtime_suspended(dev)) { + /* Ensure reconfigure during next xfer */ + sspi->speed = 0; + + clk_prepare_enable(sspi->clk[IPCLK]); + ret = clk_prepare_enable(sspi->clk[IHCLK]); + if (ret < 0) { + dev_err(dev, "failed to enable clk (%d)\n", ret); + return ret; + } + + ret = synquacer_spi_enable(master); + if (ret) { + dev_err(dev, "failed to enable spi (%d)\n", ret); + return ret; + } + } + + ret = spi_master_resume(master); + if (ret < 0) { + clk_disable_unprepare(sspi->clk[IHCLK]); + clk_disable_unprepare(sspi->clk[IPCLK]); + } + + return ret; +} +#endif /* CONFIG_PM_SLEEP */ + +static const struct dev_pm_ops synquacer_spi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(synquacer_spi_suspend, synquacer_spi_resume) +}; + +static const struct of_device_id synquacer_spi_of_match[] = { + {.compatible = "socionext,synquacer-spi",}, + {}, +}; +MODULE_DEVICE_TABLE(of, synquacer_spi_of_match); + +static struct platform_driver synquacer_spi_driver = { + .driver = { + .name = "synquacer-spi", + .pm = &synquacer_spi_pm_ops, + .of_match_table = of_match_ptr(synquacer_spi_of_match), + }, + .probe = synquacer_spi_probe, + .remove = synquacer_spi_remove, +}; +module_platform_driver(synquacer_spi_driver); + +MODULE_DESCRIPTION("Socionext Synquacer HS-SPI controller driver"); +MODULE_AUTHOR("Jassi Brar <jaswinder.singh@xxxxxxxxxx>"); +MODULE_LICENSE("GPL v2"); -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html