mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-27 21:43:45 +00:00
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,
|
||
|
};
|