492 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			492 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
| // 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 TFLETE		4
 | |
| #define RFMTE		5
 | |
| 
 | |
| #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);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 
 | |
| 	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 */
 | |
| 	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 {
 | |
| 			u32 len;
 | |
| 
 | |
| 			do { /* wait for shifter to empty out */
 | |
| 				cpu_relax();
 | |
| 				len = readl(priv->base + DMSTATUS);
 | |
| 				len = (len >> TX_DATA_SHIFT) & TX_DATA_MASK;
 | |
| 			} while (tx_buf && len);
 | |
| 			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,
 | |
| };
 |