| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * spi-synquacer.c - Socionext Synquacer SPI driver |
| * Copyright 2021 Linaro Ltd. |
| * Copyright 2021 Socionext, Inc. |
| */ |
| |
| #include <clk.h> |
| #include <common.h> |
| #include <dm.h> |
| #include <log.h> |
| #include <time.h> |
| #include <dm/device_compat.h> |
| #include <linux/bitfield.h> |
| #include <linux/bitops.h> |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <spi.h> |
| #include <wait_bit.h> |
| |
| #define MCTRL 0x0 |
| #define MEN 0 |
| #define CSEN 1 |
| #define IPCLK 3 |
| #define MES 4 |
| #define SYNCON 5 |
| |
| #define PCC0 0x4 |
| #define PCC(n) (PCC0 + (n) * 4) |
| #define RTM 3 |
| #define ACES 2 |
| #define SAFESYNC 16 |
| #define CPHA 0 |
| #define CPOL 1 |
| #define SSPOL 4 |
| #define SDIR 7 |
| #define SS2CD 5 |
| #define SENDIAN 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 TFES 1 |
| #define TFLETE 4 |
| #define TSSRS 6 |
| #define RFMTE 5 |
| #define RSSRS 6 |
| |
| #define FAULTF 0x2c |
| #define FAULTC 0x30 |
| |
| #define DMCFG 0x34 |
| #define SSDC 1 |
| #define MSTARTEN 2 |
| |
| #define DMSTART 0x38 |
| #define TRIGGER 0 |
| #define DMSTOP 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 11 |
| #define TX_FLUSH 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 1 |
| #define RXBIT 2 |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| struct synquacer_spi_plat { |
| void __iomem *base; |
| bool aces, rtm; |
| }; |
| |
| struct synquacer_spi_priv { |
| void __iomem *base; |
| bool aces, rtm; |
| int speed, cs, mode, rwflag; |
| void *rx_buf; |
| const void *tx_buf; |
| unsigned int tx_words, rx_words; |
| }; |
| |
| static void read_fifo(struct synquacer_spi_priv *priv) |
| { |
| u32 len = readl(priv->base + DMSTATUS); |
| u8 *buf = priv->rx_buf; |
| int i; |
| |
| len = (len >> RX_DATA_SHIFT) & RX_DATA_MASK; |
| len = min_t(unsigned int, len, priv->rx_words); |
| |
| for (i = 0; i < len; i++) |
| *buf++ = readb(priv->base + RXFIFO); |
| |
| priv->rx_buf = buf; |
| priv->rx_words -= len; |
| } |
| |
| static void write_fifo(struct synquacer_spi_priv *priv) |
| { |
| u32 len = readl(priv->base + DMSTATUS); |
| const u8 *buf = priv->tx_buf; |
| int i; |
| |
| len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK; |
| len = min_t(unsigned int, FIFO_DEPTH - len, priv->tx_words); |
| |
| for (i = 0; i < len; i++) |
| writeb(*buf++, priv->base + TXFIFO); |
| |
| priv->tx_buf = buf; |
| priv->tx_words -= len; |
| } |
| |
| static void synquacer_cs_set(struct synquacer_spi_priv *priv, bool active) |
| { |
| u32 val; |
| |
| val = readl(priv->base + DMSTART); |
| val &= ~(CS_MASK << CS_SHIFT); |
| val |= priv->cs << CS_SHIFT; |
| |
| if (active) { |
| writel(val, priv->base + DMSTART); |
| |
| val = readl(priv->base + DMSTART); |
| val &= ~BIT(DMSTOP); |
| writel(val, priv->base + DMSTART); |
| } else { |
| val |= BIT(DMSTOP); |
| writel(val, priv->base + DMSTART); |
| |
| if (priv->rx_buf) { |
| u32 buf[16]; |
| |
| priv->rx_buf = buf; |
| priv->rx_words = 16; |
| read_fifo(priv); |
| } |
| |
| /* wait until slave is deselected */ |
| while (!(readl(priv->base + TXF) & BIT(TSSRS)) || |
| !(readl(priv->base + RXF) & BIT(RSSRS))) |
| ; |
| } |
| } |
| |
| static void synquacer_spi_config(struct udevice *dev, void *rx, const void *tx) |
| { |
| struct udevice *bus = dev->parent; |
| struct synquacer_spi_priv *priv = dev_get_priv(bus); |
| struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev); |
| u32 val, div, bus_width; |
| int rwflag; |
| |
| rwflag = (rx ? 1 : 0) | (tx ? 2 : 0); |
| |
| /* if nothing to do */ |
| if (slave_plat->mode == priv->mode && |
| rwflag == priv->rwflag && |
| slave_plat->cs == priv->cs && |
| slave_plat->max_hz == priv->speed) |
| return; |
| |
| priv->rwflag = rwflag; |
| priv->cs = slave_plat->cs; |
| priv->mode = slave_plat->mode; |
| priv->speed = slave_plat->max_hz; |
| |
| if (priv->mode & SPI_TX_BYTE) |
| bus_width = 1; |
| else if (priv->mode & SPI_TX_DUAL) |
| bus_width = 2; |
| else if (priv->mode & SPI_TX_QUAD) |
| bus_width = 4; |
| else if (priv->mode & SPI_TX_OCTAL) |
| bus_width = 8; |
| |
| div = DIV_ROUND_UP(125000000, priv->speed); |
| |
| val = readl(priv->base + PCC(priv->cs)); |
| val &= ~BIT(RTM); |
| val &= ~BIT(ACES); |
| val &= ~BIT(SAFESYNC); |
| if ((priv->mode & (SPI_TX_DUAL | SPI_RX_DUAL)) && div < 3) |
| val |= BIT(SAFESYNC); |
| if ((priv->mode & (SPI_TX_QUAD | SPI_RX_QUAD)) && div < 6) |
| val |= BIT(SAFESYNC); |
| |
| if (priv->mode & SPI_CPHA) |
| val |= BIT(CPHA); |
| else |
| val &= ~BIT(CPHA); |
| |
| if (priv->mode & SPI_CPOL) |
| val |= BIT(CPOL); |
| else |
| val &= ~BIT(CPOL); |
| |
| if (priv->mode & SPI_CS_HIGH) |
| val |= BIT(SSPOL); |
| else |
| val &= ~BIT(SSPOL); |
| |
| if (priv->mode & SPI_LSB_FIRST) |
| val |= BIT(SDIR); |
| else |
| val &= ~BIT(SDIR); |
| |
| if (priv->aces) |
| val |= BIT(ACES); |
| |
| if (priv->rtm) |
| val |= BIT(RTM); |
| |
| val |= (3 << SS2CD); |
| val |= BIT(SENDIAN); |
| |
| val &= ~(CDRS_MASK << CDRS_SHIFT); |
| val |= ((div >> 1) << CDRS_SHIFT); |
| |
| writel(val, priv->base + PCC(priv->cs)); |
| |
| val = readl(priv->base + FIFOCFG); |
| val &= ~(BPW_MASK << BPW_SHIFT); |
| val |= (0 << BPW_SHIFT); |
| writel(val, priv->base + FIFOCFG); |
| |
| val = readl(priv->base + DMSTART); |
| val &= ~(DATA_MASK << DATA_SHIFT); |
| |
| if (tx && rx) |
| val |= (DATA_TXRX << DATA_SHIFT); |
| else if (rx) |
| val |= (DATA_RX << DATA_SHIFT); |
| else |
| val |= (DATA_TX << DATA_SHIFT); |
| |
| val &= ~(3 << BUS_WIDTH); |
| val |= ((bus_width >> 1) << BUS_WIDTH); |
| writel(val, priv->base + DMSTART); |
| } |
| |
| static int synquacer_spi_xfer(struct udevice *dev, unsigned int bitlen, |
| const void *tx_buf, void *rx_buf, |
| unsigned long flags) |
| { |
| struct udevice *bus = dev->parent; |
| struct synquacer_spi_priv *priv = dev_get_priv(bus); |
| u32 val, words, busy = 0; |
| |
| val = readl(priv->base + FIFOCFG); |
| val |= (1 << RX_FLUSH); |
| val |= (1 << TX_FLUSH); |
| writel(val, priv->base + FIFOCFG); |
| |
| synquacer_spi_config(dev, rx_buf, tx_buf); |
| |
| priv->tx_buf = tx_buf; |
| priv->rx_buf = rx_buf; |
| |
| words = bitlen / 8; |
| |
| if (tx_buf) { |
| busy |= BIT(TXBIT); |
| priv->tx_words = words; |
| } else { |
| busy &= ~BIT(TXBIT); |
| priv->tx_words = 0; |
| } |
| |
| if (rx_buf) { |
| busy |= BIT(RXBIT); |
| priv->rx_words = words; |
| } else { |
| busy &= ~BIT(RXBIT); |
| priv->rx_words = 0; |
| } |
| |
| if (flags & SPI_XFER_BEGIN) |
| synquacer_cs_set(priv, true); |
| |
| if (tx_buf) |
| write_fifo(priv); |
| |
| if (rx_buf) { |
| val = readl(priv->base + FIFOCFG); |
| val &= ~(RX_TRSHLD_MASK << RX_TRSHLD_SHIFT); |
| val |= ((priv->rx_words > FIFO_DEPTH ? |
| RX_TRSHLD : priv->rx_words) << RX_TRSHLD_SHIFT); |
| writel(val, priv->base + FIFOCFG); |
| } |
| |
| writel(~0, priv->base + TXC); |
| writel(~0, priv->base + RXC); |
| |
| /* Trigger */ |
| if (flags & SPI_XFER_BEGIN) { |
| val = readl(priv->base + DMSTART); |
| val |= BIT(TRIGGER); |
| writel(val, priv->base + DMSTART); |
| } |
| |
| while (busy & (BIT(RXBIT) | BIT(TXBIT))) { |
| if (priv->rx_words) |
| read_fifo(priv); |
| else |
| busy &= ~BIT(RXBIT); |
| |
| if (priv->tx_words) { |
| write_fifo(priv); |
| } else { |
| /* wait for shifter to empty out */ |
| while (!(readl(priv->base + TXF) & BIT(TFES))) |
| cpu_relax(); |
| |
| busy &= ~BIT(TXBIT); |
| } |
| } |
| |
| if (flags & SPI_XFER_END) |
| synquacer_cs_set(priv, false); |
| |
| return 0; |
| } |
| |
| static int synquacer_spi_set_speed(struct udevice *bus, uint speed) |
| { |
| return 0; |
| } |
| |
| static int synquacer_spi_set_mode(struct udevice *bus, uint mode) |
| { |
| return 0; |
| } |
| |
| static int synquacer_spi_claim_bus(struct udevice *dev) |
| { |
| return 0; |
| } |
| |
| static int synquacer_spi_release_bus(struct udevice *dev) |
| { |
| return 0; |
| } |
| |
| static void synquacer_spi_disable_module(struct synquacer_spi_priv *priv) |
| { |
| writel(0, priv->base + MCTRL); |
| while (readl(priv->base + MCTRL) & BIT(MES)) |
| cpu_relax(); |
| } |
| |
| static void synquacer_spi_init(struct synquacer_spi_priv *priv) |
| { |
| u32 val; |
| |
| synquacer_spi_disable_module(priv); |
| |
| writel(0, priv->base + TXE); |
| writel(0, priv->base + RXE); |
| val = readl(priv->base + TXF); |
| writel(val, priv->base + TXC); |
| val = readl(priv->base + RXF); |
| writel(val, priv->base + RXC); |
| val = readl(priv->base + FAULTF); |
| writel(val, priv->base + FAULTC); |
| |
| val = readl(priv->base + DMCFG); |
| val &= ~BIT(SSDC); |
| val &= ~BIT(MSTARTEN); |
| writel(val, priv->base + DMCFG); |
| |
| /* Enable module with direct mode */ |
| val = readl(priv->base + MCTRL); |
| val &= ~BIT(IPCLK); |
| val &= ~BIT(CSEN); |
| val |= BIT(MEN); |
| val |= BIT(SYNCON); |
| writel(val, priv->base + MCTRL); |
| } |
| |
| static void synquacer_spi_exit(struct synquacer_spi_priv *priv) |
| { |
| u32 val; |
| |
| synquacer_spi_disable_module(priv); |
| |
| /* Enable module with command sequence mode */ |
| val = readl(priv->base + MCTRL); |
| val &= ~BIT(IPCLK); |
| val |= BIT(CSEN); |
| val |= BIT(MEN); |
| val |= BIT(SYNCON); |
| writel(val, priv->base + MCTRL); |
| |
| while (!(readl(priv->base + MCTRL) & BIT(MES))) |
| cpu_relax(); |
| } |
| |
| static int synquacer_spi_probe(struct udevice *bus) |
| { |
| struct synquacer_spi_plat *plat = dev_get_plat(bus); |
| struct synquacer_spi_priv *priv = dev_get_priv(bus); |
| |
| priv->base = plat->base; |
| priv->aces = plat->aces; |
| priv->rtm = plat->rtm; |
| |
| synquacer_spi_init(priv); |
| return 0; |
| } |
| |
| static int synquacer_spi_remove(struct udevice *bus) |
| { |
| struct synquacer_spi_priv *priv = dev_get_priv(bus); |
| |
| synquacer_spi_exit(priv); |
| return 0; |
| } |
| |
| static int synquacer_spi_of_to_plat(struct udevice *bus) |
| { |
| struct synquacer_spi_plat *plat = dev_get_plat(bus); |
| struct clk clk; |
| |
| plat->base = dev_read_addr_ptr(bus); |
| |
| plat->aces = dev_read_bool(bus, "socionext,set-aces"); |
| plat->rtm = dev_read_bool(bus, "socionext,use-rtm"); |
| |
| clk_get_by_name(bus, "iHCLK", &clk); |
| clk_enable(&clk); |
| |
| return 0; |
| } |
| |
| static const struct dm_spi_ops synquacer_spi_ops = { |
| .claim_bus = synquacer_spi_claim_bus, |
| .release_bus = synquacer_spi_release_bus, |
| .xfer = synquacer_spi_xfer, |
| .set_speed = synquacer_spi_set_speed, |
| .set_mode = synquacer_spi_set_mode, |
| }; |
| |
| static const struct udevice_id synquacer_spi_ids[] = { |
| { .compatible = "socionext,synquacer-spi" }, |
| { /* Sentinel */ } |
| }; |
| |
| U_BOOT_DRIVER(synquacer_spi) = { |
| .name = "synquacer_spi", |
| .id = UCLASS_SPI, |
| .of_match = synquacer_spi_ids, |
| .ops = &synquacer_spi_ops, |
| .of_to_plat = synquacer_spi_of_to_plat, |
| .plat_auto = sizeof(struct synquacer_spi_plat), |
| .priv_auto = sizeof(struct synquacer_spi_priv), |
| .probe = synquacer_spi_probe, |
| .flags = DM_FLAG_OS_PREPARE, |
| .remove = synquacer_spi_remove, |
| }; |