// SPDX-License-Identifier: GPL-2.0+ /* * spi-synquacer.c - Socionext Synquacer SPI driver * Copyright 2021 Linaro Ltd. * Copyright 2021 Socionext, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #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_DUAL) bus_width = 2; else if (priv->mode & SPI_TX_QUAD) bus_width = 4; else if (priv->mode & SPI_TX_OCTAL) bus_width = 8; else bus_width = 1; /* default is single bit mode */ 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, };