|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | // | 
|  | // Synquacer HSSPI controller driver | 
|  | // | 
|  | // Copyright (c) 2015-2018 Socionext Inc. | 
|  | // Copyright (c) 2018-2019 Linaro Ltd. | 
|  | // | 
|  |  | 
|  | #include <linux/acpi.h> | 
|  | #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> | 
|  |  | 
|  | /* HSSPI register address definitions */ | 
|  | #define SYNQUACER_HSSPI_REG_MCTRL	0x00 | 
|  | #define SYNQUACER_HSSPI_REG_PCC0	0x04 | 
|  | #define SYNQUACER_HSSPI_REG_PCC(n)	(SYNQUACER_HSSPI_REG_PCC0 + (n) * 4) | 
|  | #define SYNQUACER_HSSPI_REG_TXF		0x14 | 
|  | #define SYNQUACER_HSSPI_REG_TXE		0x18 | 
|  | #define SYNQUACER_HSSPI_REG_TXC		0x1C | 
|  | #define SYNQUACER_HSSPI_REG_RXF		0x20 | 
|  | #define SYNQUACER_HSSPI_REG_RXE		0x24 | 
|  | #define SYNQUACER_HSSPI_REG_RXC		0x28 | 
|  | #define SYNQUACER_HSSPI_REG_FAULTF	0x2C | 
|  | #define SYNQUACER_HSSPI_REG_FAULTC	0x30 | 
|  | #define SYNQUACER_HSSPI_REG_DMCFG	0x34 | 
|  | #define SYNQUACER_HSSPI_REG_DMSTART	0x38 | 
|  | #define SYNQUACER_HSSPI_REG_DMBCC	0x3C | 
|  | #define SYNQUACER_HSSPI_REG_DMSTATUS	0x40 | 
|  | #define SYNQUACER_HSSPI_REG_FIFOCFG	0x4C | 
|  | #define SYNQUACER_HSSPI_REG_TX_FIFO	0x50 | 
|  | #define SYNQUACER_HSSPI_REG_RX_FIFO	0x90 | 
|  | #define SYNQUACER_HSSPI_REG_MID		0xFC | 
|  |  | 
|  | /* HSSPI register bit definitions */ | 
|  | #define SYNQUACER_HSSPI_MCTRL_MEN			BIT(0) | 
|  | #define SYNQUACER_HSSPI_MCTRL_COMMAND_SEQUENCE_EN	BIT(1) | 
|  | #define SYNQUACER_HSSPI_MCTRL_CDSS			BIT(3) | 
|  | #define SYNQUACER_HSSPI_MCTRL_MES			BIT(4) | 
|  | #define SYNQUACER_HSSPI_MCTRL_SYNCON			BIT(5) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_PCC_CPHA		BIT(0) | 
|  | #define SYNQUACER_HSSPI_PCC_CPOL		BIT(1) | 
|  | #define SYNQUACER_HSSPI_PCC_ACES		BIT(2) | 
|  | #define SYNQUACER_HSSPI_PCC_RTM			BIT(3) | 
|  | #define SYNQUACER_HSSPI_PCC_SSPOL		BIT(4) | 
|  | #define SYNQUACER_HSSPI_PCC_SDIR		BIT(7) | 
|  | #define SYNQUACER_HSSPI_PCC_SENDIAN		BIT(8) | 
|  | #define SYNQUACER_HSSPI_PCC_SAFESYNC		BIT(16) | 
|  | #define SYNQUACER_HSSPI_PCC_SS2CD_SHIFT		5U | 
|  | #define SYNQUACER_HSSPI_PCC_CDRS_MASK		0x7f | 
|  | #define SYNQUACER_HSSPI_PCC_CDRS_SHIFT		9U | 
|  |  | 
|  | #define SYNQUACER_HSSPI_TXF_FIFO_FULL		BIT(0) | 
|  | #define SYNQUACER_HSSPI_TXF_FIFO_EMPTY		BIT(1) | 
|  | #define SYNQUACER_HSSPI_TXF_SLAVE_RELEASED	BIT(6) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_TXE_FIFO_FULL		BIT(0) | 
|  | #define SYNQUACER_HSSPI_TXE_FIFO_EMPTY		BIT(1) | 
|  | #define SYNQUACER_HSSPI_TXE_SLAVE_RELEASED	BIT(6) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_RXF_FIFO_MORE_THAN_THRESHOLD		BIT(5) | 
|  | #define SYNQUACER_HSSPI_RXF_SLAVE_RELEASED			BIT(6) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_RXE_FIFO_MORE_THAN_THRESHOLD		BIT(5) | 
|  | #define SYNQUACER_HSSPI_RXE_SLAVE_RELEASED			BIT(6) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_DMCFG_SSDC		BIT(1) | 
|  | #define SYNQUACER_HSSPI_DMCFG_MSTARTEN		BIT(2) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_DMSTART_START		BIT(0) | 
|  | #define SYNQUACER_HSSPI_DMSTOP_STOP		BIT(8) | 
|  | #define SYNQUACER_HSSPI_DMPSEL_CS_MASK		0x3 | 
|  | #define SYNQUACER_HSSPI_DMPSEL_CS_SHIFT		16U | 
|  | #define SYNQUACER_HSSPI_DMTRP_BUS_WIDTH_SHIFT	24U | 
|  | #define SYNQUACER_HSSPI_DMTRP_DATA_MASK		0x3 | 
|  | #define SYNQUACER_HSSPI_DMTRP_DATA_SHIFT	26U | 
|  | #define SYNQUACER_HSSPI_DMTRP_DATA_TXRX		0 | 
|  | #define SYNQUACER_HSSPI_DMTRP_DATA_RX		1 | 
|  | #define SYNQUACER_HSSPI_DMTRP_DATA_TX		2 | 
|  |  | 
|  | #define SYNQUACER_HSSPI_DMSTATUS_RX_DATA_MASK	0x1f | 
|  | #define SYNQUACER_HSSPI_DMSTATUS_RX_DATA_SHIFT	8U | 
|  | #define SYNQUACER_HSSPI_DMSTATUS_TX_DATA_MASK	0x1f | 
|  | #define SYNQUACER_HSSPI_DMSTATUS_TX_DATA_SHIFT	16U | 
|  |  | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_RX_THRESHOLD_MASK	0xf | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_RX_THRESHOLD_SHIFT	0U | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_TX_THRESHOLD_MASK	0xf | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_TX_THRESHOLD_SHIFT	4U | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_FIFO_WIDTH_MASK		0x3 | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_FIFO_WIDTH_SHIFT	8U | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_RX_FLUSH		BIT(11) | 
|  | #define SYNQUACER_HSSPI_FIFOCFG_TX_FLUSH		BIT(12) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_FIFO_DEPTH		16U | 
|  | #define SYNQUACER_HSSPI_FIFO_TX_THRESHOLD	4U | 
|  | #define SYNQUACER_HSSPI_FIFO_RX_THRESHOLD \ | 
|  | (SYNQUACER_HSSPI_FIFO_DEPTH - SYNQUACER_HSSPI_FIFO_TX_THRESHOLD) | 
|  |  | 
|  | #define SYNQUACER_HSSPI_TRANSFER_MODE_TX	BIT(1) | 
|  | #define SYNQUACER_HSSPI_TRANSFER_MODE_RX	BIT(2) | 
|  | #define SYNQUACER_HSSPI_TRANSFER_TMOUT_MSEC	2000U | 
|  | #define SYNQUACER_HSSPI_ENABLE_TMOUT_MSEC	1000U | 
|  |  | 
|  | #define SYNQUACER_HSSPI_CLOCK_SRC_IHCLK		0 | 
|  | #define SYNQUACER_HSSPI_CLOCK_SRC_IPCLK		1 | 
|  |  | 
|  | #define SYNQUACER_HSSPI_NUM_CHIP_SELECT		4U | 
|  | #define SYNQUACER_HSSPI_IRQ_NAME_MAX		32U | 
|  |  | 
|  | struct synquacer_spi { | 
|  | struct device *dev; | 
|  | struct completion transfer_done; | 
|  | 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; | 
|  | int clk_src_type; | 
|  | void __iomem *regs; | 
|  | u32 tx_words, rx_words; | 
|  | unsigned int bus_width; | 
|  | unsigned int transfer_mode; | 
|  | char rx_irq_name[SYNQUACER_HSSPI_IRQ_NAME_MAX]; | 
|  | char tx_irq_name[SYNQUACER_HSSPI_IRQ_NAME_MAX]; | 
|  | }; | 
|  |  | 
|  | static int read_fifo(struct synquacer_spi *sspi) | 
|  | { | 
|  | u32 len = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTATUS); | 
|  |  | 
|  | len = (len >> SYNQUACER_HSSPI_DMSTATUS_RX_DATA_SHIFT) & | 
|  | SYNQUACER_HSSPI_DMSTATUS_RX_DATA_MASK; | 
|  | len = min(len, sspi->rx_words); | 
|  |  | 
|  | switch (sspi->bpw) { | 
|  | case 8: { | 
|  | u8 *buf = sspi->rx_buf; | 
|  |  | 
|  | ioread8_rep(sspi->regs + SYNQUACER_HSSPI_REG_RX_FIFO, | 
|  | buf, len); | 
|  | sspi->rx_buf = buf + len; | 
|  | break; | 
|  | } | 
|  | case 16: { | 
|  | u16 *buf = sspi->rx_buf; | 
|  |  | 
|  | ioread16_rep(sspi->regs + SYNQUACER_HSSPI_REG_RX_FIFO, | 
|  | buf, len); | 
|  | sspi->rx_buf = buf + len; | 
|  | break; | 
|  | } | 
|  | case 24: | 
|  | /* fallthrough, should use 32-bits access */ | 
|  | case 32: { | 
|  | u32 *buf = sspi->rx_buf; | 
|  |  | 
|  | ioread32_rep(sspi->regs + SYNQUACER_HSSPI_REG_RX_FIFO, | 
|  | buf, len); | 
|  | sspi->rx_buf = buf + len; | 
|  | break; | 
|  | } | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | sspi->rx_words -= len; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int write_fifo(struct synquacer_spi *sspi) | 
|  | { | 
|  | u32 len = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTATUS); | 
|  |  | 
|  | len = (len >> SYNQUACER_HSSPI_DMSTATUS_TX_DATA_SHIFT) & | 
|  | SYNQUACER_HSSPI_DMSTATUS_TX_DATA_MASK; | 
|  | len = min(SYNQUACER_HSSPI_FIFO_DEPTH - len, | 
|  | sspi->tx_words); | 
|  |  | 
|  | switch (sspi->bpw) { | 
|  | case 8: { | 
|  | const u8 *buf = sspi->tx_buf; | 
|  |  | 
|  | iowrite8_rep(sspi->regs + SYNQUACER_HSSPI_REG_TX_FIFO, | 
|  | buf, len); | 
|  | sspi->tx_buf = buf + len; | 
|  | break; | 
|  | } | 
|  | case 16: { | 
|  | const u16 *buf = sspi->tx_buf; | 
|  |  | 
|  | iowrite16_rep(sspi->regs + SYNQUACER_HSSPI_REG_TX_FIFO, | 
|  | buf, len); | 
|  | sspi->tx_buf = buf + len; | 
|  | break; | 
|  | } | 
|  | case 24: | 
|  | /* fallthrough, should use 32-bits access */ | 
|  | case 32: { | 
|  | const u32 *buf = sspi->tx_buf; | 
|  |  | 
|  | iowrite32_rep(sspi->regs + SYNQUACER_HSSPI_REG_TX_FIFO, | 
|  | buf, len); | 
|  | sspi->tx_buf = buf + len; | 
|  | break; | 
|  | } | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | sspi->tx_words -= len; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int synquacer_spi_config(struct spi_controller *host, | 
|  | struct spi_device *spi, | 
|  | struct spi_transfer *xfer) | 
|  | { | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(host); | 
|  | unsigned int speed, mode, bpw, cs, bus_width, transfer_mode; | 
|  | u32 rate, val, div; | 
|  |  | 
|  | /* Full Duplex only on 1-bit 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 be 1-bit for Full-Duplex!\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (xfer->tx_buf) { | 
|  | bus_width = xfer->tx_nbits; | 
|  | transfer_mode = SYNQUACER_HSSPI_TRANSFER_MODE_TX; | 
|  | } else { | 
|  | bus_width = xfer->rx_nbits; | 
|  | transfer_mode = SYNQUACER_HSSPI_TRANSFER_MODE_RX; | 
|  | } | 
|  |  | 
|  | mode = spi->mode; | 
|  | cs = spi_get_chipselect(spi, 0); | 
|  | 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 && | 
|  | transfer_mode == sspi->transfer_mode) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | sspi->transfer_mode = transfer_mode; | 
|  | rate = host->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(sspi->regs + SYNQUACER_HSSPI_REG_PCC(cs)); | 
|  | val &= ~SYNQUACER_HSSPI_PCC_SAFESYNC; | 
|  | if (bpw == 8 &&	(mode & (SPI_TX_DUAL | SPI_RX_DUAL)) && div < 3) | 
|  | val |= SYNQUACER_HSSPI_PCC_SAFESYNC; | 
|  | if (bpw == 8 &&	(mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 6) | 
|  | val |= SYNQUACER_HSSPI_PCC_SAFESYNC; | 
|  | if (bpw == 16 && (mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 3) | 
|  | val |= SYNQUACER_HSSPI_PCC_SAFESYNC; | 
|  |  | 
|  | if (mode & SPI_CPHA) | 
|  | val |= SYNQUACER_HSSPI_PCC_CPHA; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_PCC_CPHA; | 
|  |  | 
|  | if (mode & SPI_CPOL) | 
|  | val |= SYNQUACER_HSSPI_PCC_CPOL; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_PCC_CPOL; | 
|  |  | 
|  | if (mode & SPI_CS_HIGH) | 
|  | val |= SYNQUACER_HSSPI_PCC_SSPOL; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_PCC_SSPOL; | 
|  |  | 
|  | if (mode & SPI_LSB_FIRST) | 
|  | val |= SYNQUACER_HSSPI_PCC_SDIR; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_PCC_SDIR; | 
|  |  | 
|  | if (sspi->aces) | 
|  | val |= SYNQUACER_HSSPI_PCC_ACES; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_PCC_ACES; | 
|  |  | 
|  | if (sspi->rtm) | 
|  | val |= SYNQUACER_HSSPI_PCC_RTM; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_PCC_RTM; | 
|  |  | 
|  | val |= (3 << SYNQUACER_HSSPI_PCC_SS2CD_SHIFT); | 
|  | val |= SYNQUACER_HSSPI_PCC_SENDIAN; | 
|  |  | 
|  | val &= ~(SYNQUACER_HSSPI_PCC_CDRS_MASK << | 
|  | SYNQUACER_HSSPI_PCC_CDRS_SHIFT); | 
|  | val |= ((div >> 1) << SYNQUACER_HSSPI_PCC_CDRS_SHIFT); | 
|  |  | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_PCC(cs)); | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_FIFOCFG); | 
|  | val &= ~(SYNQUACER_HSSPI_FIFOCFG_FIFO_WIDTH_MASK << | 
|  | SYNQUACER_HSSPI_FIFOCFG_FIFO_WIDTH_SHIFT); | 
|  | val |= ((bpw / 8 - 1) << SYNQUACER_HSSPI_FIFOCFG_FIFO_WIDTH_SHIFT); | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_FIFOCFG); | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | val &= ~(SYNQUACER_HSSPI_DMTRP_DATA_MASK << | 
|  | SYNQUACER_HSSPI_DMTRP_DATA_SHIFT); | 
|  |  | 
|  | if (xfer->rx_buf) | 
|  | val |= (SYNQUACER_HSSPI_DMTRP_DATA_RX << | 
|  | SYNQUACER_HSSPI_DMTRP_DATA_SHIFT); | 
|  | else | 
|  | val |= (SYNQUACER_HSSPI_DMTRP_DATA_TX << | 
|  | SYNQUACER_HSSPI_DMTRP_DATA_SHIFT); | 
|  |  | 
|  | val &= ~(3 << SYNQUACER_HSSPI_DMTRP_BUS_WIDTH_SHIFT); | 
|  | val |= ((bus_width >> 1) << SYNQUACER_HSSPI_DMTRP_BUS_WIDTH_SHIFT); | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  |  | 
|  | sspi->bpw = bpw; | 
|  | sspi->mode = mode; | 
|  | sspi->speed = speed; | 
|  | sspi->cs = spi_get_chipselect(spi, 0); | 
|  | sspi->bus_width = bus_width; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int synquacer_spi_transfer_one(struct spi_controller *host, | 
|  | struct spi_device *spi, | 
|  | struct spi_transfer *xfer) | 
|  | { | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(host); | 
|  | int ret; | 
|  | int status = 0; | 
|  | u32 words; | 
|  | u8 bpw; | 
|  | u32 val; | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | val &= ~SYNQUACER_HSSPI_DMSTOP_STOP; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_FIFOCFG); | 
|  | val |= SYNQUACER_HSSPI_FIFOCFG_RX_FLUSH; | 
|  | val |= SYNQUACER_HSSPI_FIFOCFG_TX_FLUSH; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_FIFOCFG); | 
|  |  | 
|  | /* | 
|  | * See if we can transfer 4-bytes as 1 word | 
|  | * to maximize the FIFO buffer efficiency. | 
|  | */ | 
|  | 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(host, spi, xfer); | 
|  |  | 
|  | /* restore */ | 
|  | xfer->bits_per_word = bpw; | 
|  |  | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | reinit_completion(&sspi->transfer_done); | 
|  |  | 
|  | 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; | 
|  | case 24: | 
|  | /* fallthrough, should use 32-bits access */ | 
|  | case 32: | 
|  | words = xfer->len / 4; | 
|  | break; | 
|  | default: | 
|  | dev_err(sspi->dev, "unsupported bpw: %d\n", sspi->bpw); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (xfer->tx_buf) | 
|  | sspi->tx_words = words; | 
|  | else | 
|  | sspi->tx_words = 0; | 
|  |  | 
|  | if (xfer->rx_buf) | 
|  | sspi->rx_words = words; | 
|  | else | 
|  | sspi->rx_words = 0; | 
|  |  | 
|  | if (xfer->tx_buf) { | 
|  | status = write_fifo(sspi); | 
|  | if (status < 0) { | 
|  | dev_err(sspi->dev, "failed write_fifo. status: 0x%x\n", | 
|  | status); | 
|  | return status; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (xfer->rx_buf) { | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_FIFOCFG); | 
|  | val &= ~(SYNQUACER_HSSPI_FIFOCFG_RX_THRESHOLD_MASK << | 
|  | SYNQUACER_HSSPI_FIFOCFG_RX_THRESHOLD_SHIFT); | 
|  | val |= ((sspi->rx_words > SYNQUACER_HSSPI_FIFO_DEPTH ? | 
|  | SYNQUACER_HSSPI_FIFO_RX_THRESHOLD : sspi->rx_words) << | 
|  | SYNQUACER_HSSPI_FIFOCFG_RX_THRESHOLD_SHIFT); | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_FIFOCFG); | 
|  | } | 
|  |  | 
|  | writel(~0, sspi->regs + SYNQUACER_HSSPI_REG_TXC); | 
|  | writel(~0, sspi->regs + SYNQUACER_HSSPI_REG_RXC); | 
|  |  | 
|  | /* Trigger */ | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | val |= SYNQUACER_HSSPI_DMSTART_START; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  |  | 
|  | if (xfer->tx_buf) { | 
|  | val = SYNQUACER_HSSPI_TXE_FIFO_EMPTY; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_TXE); | 
|  | status = wait_for_completion_timeout(&sspi->transfer_done, | 
|  | msecs_to_jiffies(SYNQUACER_HSSPI_TRANSFER_TMOUT_MSEC)); | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_TXE); | 
|  | } | 
|  |  | 
|  | if (xfer->rx_buf) { | 
|  | u32 buf[SYNQUACER_HSSPI_FIFO_DEPTH]; | 
|  |  | 
|  | val = SYNQUACER_HSSPI_RXE_FIFO_MORE_THAN_THRESHOLD | | 
|  | SYNQUACER_HSSPI_RXE_SLAVE_RELEASED; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_RXE); | 
|  | status = wait_for_completion_timeout(&sspi->transfer_done, | 
|  | msecs_to_jiffies(SYNQUACER_HSSPI_TRANSFER_TMOUT_MSEC)); | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_RXE); | 
|  |  | 
|  | /* stop RX and clean RXFIFO */ | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | val |= SYNQUACER_HSSPI_DMSTOP_STOP; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | sspi->rx_buf = buf; | 
|  | sspi->rx_words = SYNQUACER_HSSPI_FIFO_DEPTH; | 
|  | read_fifo(sspi); | 
|  | } | 
|  |  | 
|  | if (status == 0) { | 
|  | dev_err(sspi->dev, "failed to transfer. Timeout.\n"); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void synquacer_spi_set_cs(struct spi_device *spi, bool enable) | 
|  | { | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(spi->controller); | 
|  | u32 val; | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | val &= ~(SYNQUACER_HSSPI_DMPSEL_CS_MASK << | 
|  | SYNQUACER_HSSPI_DMPSEL_CS_SHIFT); | 
|  | val |= spi_get_chipselect(spi, 0) << SYNQUACER_HSSPI_DMPSEL_CS_SHIFT; | 
|  |  | 
|  | if (!enable) | 
|  | val |= SYNQUACER_HSSPI_DMSTOP_STOP; | 
|  |  | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMSTART); | 
|  | } | 
|  |  | 
|  | static int synquacer_spi_wait_status_update(struct synquacer_spi *sspi, | 
|  | bool enable) | 
|  | { | 
|  | u32 val; | 
|  | unsigned long timeout = jiffies + | 
|  | msecs_to_jiffies(SYNQUACER_HSSPI_ENABLE_TMOUT_MSEC); | 
|  |  | 
|  | /* wait MES(Module Enable Status) is updated */ | 
|  | do { | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_MCTRL) & | 
|  | SYNQUACER_HSSPI_MCTRL_MES; | 
|  | if (enable && val) | 
|  | return 0; | 
|  | if (!enable && !val) | 
|  | return 0; | 
|  | } while (time_before(jiffies, timeout)); | 
|  |  | 
|  | dev_err(sspi->dev, "timeout occurs in updating Module Enable Status\n"); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | static int synquacer_spi_enable(struct spi_controller *host) | 
|  | { | 
|  | u32 val; | 
|  | int status; | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(host); | 
|  |  | 
|  | /* Disable module */ | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_MCTRL); | 
|  | status = synquacer_spi_wait_status_update(sspi, false); | 
|  | if (status < 0) | 
|  | return status; | 
|  |  | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_TXE); | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_RXE); | 
|  | writel(~0, sspi->regs + SYNQUACER_HSSPI_REG_TXC); | 
|  | writel(~0, sspi->regs + SYNQUACER_HSSPI_REG_RXC); | 
|  | writel(~0, sspi->regs + SYNQUACER_HSSPI_REG_FAULTC); | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_DMCFG); | 
|  | val &= ~SYNQUACER_HSSPI_DMCFG_SSDC; | 
|  | val &= ~SYNQUACER_HSSPI_DMCFG_MSTARTEN; | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_DMCFG); | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_MCTRL); | 
|  | if (sspi->clk_src_type == SYNQUACER_HSSPI_CLOCK_SRC_IPCLK) | 
|  | val |= SYNQUACER_HSSPI_MCTRL_CDSS; | 
|  | else | 
|  | val &= ~SYNQUACER_HSSPI_MCTRL_CDSS; | 
|  |  | 
|  | val &= ~SYNQUACER_HSSPI_MCTRL_COMMAND_SEQUENCE_EN; | 
|  | val |= SYNQUACER_HSSPI_MCTRL_MEN; | 
|  | val |= SYNQUACER_HSSPI_MCTRL_SYNCON; | 
|  |  | 
|  | /* Enable module */ | 
|  | writel(val, sspi->regs + SYNQUACER_HSSPI_REG_MCTRL); | 
|  | status = synquacer_spi_wait_status_update(sspi, true); | 
|  | if (status < 0) | 
|  | return status; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static irqreturn_t sq_spi_rx_handler(int irq, void *priv) | 
|  | { | 
|  | uint32_t val; | 
|  | struct synquacer_spi *sspi = priv; | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_RXF); | 
|  | if ((val & SYNQUACER_HSSPI_RXF_SLAVE_RELEASED) || | 
|  | (val & SYNQUACER_HSSPI_RXF_FIFO_MORE_THAN_THRESHOLD)) { | 
|  | read_fifo(sspi); | 
|  |  | 
|  | if (sspi->rx_words == 0) { | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_RXE); | 
|  | complete(&sspi->transfer_done); | 
|  | } | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | return IRQ_NONE; | 
|  | } | 
|  |  | 
|  | static irqreturn_t sq_spi_tx_handler(int irq, void *priv) | 
|  | { | 
|  | uint32_t val; | 
|  | struct synquacer_spi *sspi = priv; | 
|  |  | 
|  | val = readl(sspi->regs + SYNQUACER_HSSPI_REG_TXF); | 
|  | if (val & SYNQUACER_HSSPI_TXF_FIFO_EMPTY) { | 
|  | if (sspi->tx_words == 0) { | 
|  | writel(0, sspi->regs + SYNQUACER_HSSPI_REG_TXE); | 
|  | complete(&sspi->transfer_done); | 
|  | } else { | 
|  | write_fifo(sspi); | 
|  | } | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | return IRQ_NONE; | 
|  | } | 
|  |  | 
|  | static int synquacer_spi_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct device_node *np = pdev->dev.of_node; | 
|  | struct spi_controller *host; | 
|  | struct synquacer_spi *sspi; | 
|  | int ret; | 
|  | int rx_irq, tx_irq; | 
|  |  | 
|  | host = spi_alloc_host(&pdev->dev, sizeof(*sspi)); | 
|  | if (!host) | 
|  | return -ENOMEM; | 
|  |  | 
|  | platform_set_drvdata(pdev, host); | 
|  |  | 
|  | sspi = spi_controller_get_devdata(host); | 
|  | sspi->dev = &pdev->dev; | 
|  |  | 
|  | init_completion(&sspi->transfer_done); | 
|  |  | 
|  | sspi->regs = devm_platform_ioremap_resource(pdev, 0); | 
|  | if (IS_ERR(sspi->regs)) { | 
|  | ret = PTR_ERR(sspi->regs); | 
|  | goto put_spi; | 
|  | } | 
|  |  | 
|  | sspi->clk_src_type = SYNQUACER_HSSPI_CLOCK_SRC_IHCLK; /* Default */ | 
|  | device_property_read_u32(&pdev->dev, "socionext,ihclk-rate", | 
|  | &host->max_speed_hz); /* for ACPI */ | 
|  |  | 
|  | if (dev_of_node(&pdev->dev)) { | 
|  | if (device_property_match_string(&pdev->dev, | 
|  | "clock-names", "iHCLK") >= 0) { | 
|  | sspi->clk_src_type = SYNQUACER_HSSPI_CLOCK_SRC_IHCLK; | 
|  | sspi->clk = devm_clk_get(sspi->dev, "iHCLK"); | 
|  | } else if (device_property_match_string(&pdev->dev, | 
|  | "clock-names", "iPCLK") >= 0) { | 
|  | sspi->clk_src_type = SYNQUACER_HSSPI_CLOCK_SRC_IPCLK; | 
|  | sspi->clk = devm_clk_get(sspi->dev, "iPCLK"); | 
|  | } else { | 
|  | dev_err(&pdev->dev, "specified wrong clock source\n"); | 
|  | ret = -EINVAL; | 
|  | goto put_spi; | 
|  | } | 
|  |  | 
|  | if (IS_ERR(sspi->clk)) { | 
|  | ret = dev_err_probe(&pdev->dev, PTR_ERR(sspi->clk), | 
|  | "clock not found\n"); | 
|  | goto put_spi; | 
|  | } | 
|  |  | 
|  | ret = clk_prepare_enable(sspi->clk); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "failed to enable clock (%d)\n", | 
|  | ret); | 
|  | goto put_spi; | 
|  | } | 
|  |  | 
|  | host->max_speed_hz = clk_get_rate(sspi->clk); | 
|  | } | 
|  |  | 
|  | if (!host->max_speed_hz) { | 
|  | dev_err(&pdev->dev, "missing clock source\n"); | 
|  | ret = -EINVAL; | 
|  | goto disable_clk; | 
|  | } | 
|  | host->min_speed_hz = host->max_speed_hz / 254; | 
|  |  | 
|  | sspi->aces = device_property_read_bool(&pdev->dev, | 
|  | "socionext,set-aces"); | 
|  | sspi->rtm = device_property_read_bool(&pdev->dev, "socionext,use-rtm"); | 
|  |  | 
|  | host->num_chipselect = SYNQUACER_HSSPI_NUM_CHIP_SELECT; | 
|  |  | 
|  | rx_irq = platform_get_irq(pdev, 0); | 
|  | if (rx_irq <= 0) { | 
|  | ret = rx_irq; | 
|  | goto disable_clk; | 
|  | } | 
|  | snprintf(sspi->rx_irq_name, SYNQUACER_HSSPI_IRQ_NAME_MAX, "%s-rx", | 
|  | dev_name(&pdev->dev)); | 
|  | ret = devm_request_irq(&pdev->dev, rx_irq, sq_spi_rx_handler, | 
|  | 0, sspi->rx_irq_name, sspi); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "request rx_irq failed (%d)\n", ret); | 
|  | goto disable_clk; | 
|  | } | 
|  |  | 
|  | tx_irq = platform_get_irq(pdev, 1); | 
|  | if (tx_irq <= 0) { | 
|  | ret = tx_irq; | 
|  | goto disable_clk; | 
|  | } | 
|  | snprintf(sspi->tx_irq_name, SYNQUACER_HSSPI_IRQ_NAME_MAX, "%s-tx", | 
|  | dev_name(&pdev->dev)); | 
|  | ret = devm_request_irq(&pdev->dev, tx_irq, sq_spi_tx_handler, | 
|  | 0, sspi->tx_irq_name, sspi); | 
|  | if (ret) { | 
|  | dev_err(&pdev->dev, "request tx_irq failed (%d)\n", ret); | 
|  | goto disable_clk; | 
|  | } | 
|  |  | 
|  | host->dev.of_node = np; | 
|  | host->dev.fwnode = pdev->dev.fwnode; | 
|  | host->auto_runtime_pm = true; | 
|  | host->bus_num = pdev->id; | 
|  |  | 
|  | host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_DUAL | SPI_RX_DUAL | | 
|  | SPI_TX_QUAD | SPI_RX_QUAD; | 
|  | host->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(24) | | 
|  | SPI_BPW_MASK(16) | SPI_BPW_MASK(8); | 
|  |  | 
|  | host->set_cs = synquacer_spi_set_cs; | 
|  | host->transfer_one = synquacer_spi_transfer_one; | 
|  |  | 
|  | ret = synquacer_spi_enable(host); | 
|  | if (ret) | 
|  | goto disable_clk; | 
|  |  | 
|  | pm_runtime_set_active(sspi->dev); | 
|  | pm_runtime_enable(sspi->dev); | 
|  |  | 
|  | ret = devm_spi_register_controller(sspi->dev, host); | 
|  | if (ret) | 
|  | goto disable_pm; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | disable_pm: | 
|  | pm_runtime_disable(sspi->dev); | 
|  | disable_clk: | 
|  | clk_disable_unprepare(sspi->clk); | 
|  | put_spi: | 
|  | spi_controller_put(host); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void synquacer_spi_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct spi_controller *host = platform_get_drvdata(pdev); | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(host); | 
|  |  | 
|  | pm_runtime_disable(sspi->dev); | 
|  |  | 
|  | clk_disable_unprepare(sspi->clk); | 
|  | } | 
|  |  | 
|  | static int __maybe_unused synquacer_spi_suspend(struct device *dev) | 
|  | { | 
|  | struct spi_controller *host = dev_get_drvdata(dev); | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(host); | 
|  | int ret; | 
|  |  | 
|  | ret = spi_controller_suspend(host); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (!pm_runtime_suspended(dev)) | 
|  | clk_disable_unprepare(sspi->clk); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int __maybe_unused synquacer_spi_resume(struct device *dev) | 
|  | { | 
|  | struct spi_controller *host = dev_get_drvdata(dev); | 
|  | struct synquacer_spi *sspi = spi_controller_get_devdata(host); | 
|  | int ret; | 
|  |  | 
|  | if (!pm_runtime_suspended(dev)) { | 
|  | /* Ensure reconfigure during next xfer */ | 
|  | sspi->speed = 0; | 
|  |  | 
|  | ret = clk_prepare_enable(sspi->clk); | 
|  | if (ret < 0) { | 
|  | dev_err(dev, "failed to enable clk (%d)\n", | 
|  | ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = synquacer_spi_enable(host); | 
|  | if (ret) { | 
|  | clk_disable_unprepare(sspi->clk); | 
|  | dev_err(dev, "failed to enable spi (%d)\n", ret); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | ret = spi_controller_resume(host); | 
|  | if (ret < 0) | 
|  | clk_disable_unprepare(sspi->clk); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static SIMPLE_DEV_PM_OPS(synquacer_spi_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); | 
|  |  | 
|  | #ifdef CONFIG_ACPI | 
|  | static const struct acpi_device_id synquacer_hsspi_acpi_ids[] = { | 
|  | { "SCX0004" }, | 
|  | { /* sentinel */ } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(acpi, synquacer_hsspi_acpi_ids); | 
|  | #endif | 
|  |  | 
|  | static struct platform_driver synquacer_spi_driver = { | 
|  | .driver = { | 
|  | .name = "synquacer-spi", | 
|  | .pm = &synquacer_spi_pm_ops, | 
|  | .of_match_table = synquacer_spi_of_match, | 
|  | .acpi_match_table = ACPI_PTR(synquacer_hsspi_acpi_ids), | 
|  | }, | 
|  | .probe = synquacer_spi_probe, | 
|  | .remove = synquacer_spi_remove, | 
|  | }; | 
|  | module_platform_driver(synquacer_spi_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Socionext Synquacer HS-SPI controller driver"); | 
|  | MODULE_AUTHOR("Masahisa Kojima <masahisa.kojima@linaro.org>"); | 
|  | MODULE_AUTHOR("Jassi Brar <jaswinder.singh@linaro.org>"); | 
|  | MODULE_LICENSE("GPL v2"); |