mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-25 06:00:43 +00:00
1e94b46f73
This old patch was marked as deferred. Bring it back to life, to continue towards the removal of common.h Move this out of the common header and include it only where needed. Signed-off-by: Simon Glass <sjg@chromium.org>
528 lines
12 KiB
C
528 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* (C) Copyright 2016
|
|
*
|
|
* Michael Kurz, <michi.kurz@gmail.com>
|
|
*
|
|
* STM32 QSPI driver
|
|
*/
|
|
|
|
#define LOG_CATEGORY UCLASS_SPI
|
|
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <dm.h>
|
|
#include <log.h>
|
|
#include <reset.h>
|
|
#include <spi.h>
|
|
#include <spi-mem.h>
|
|
#include <watchdog.h>
|
|
#include <dm/device_compat.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/sizes.h>
|
|
|
|
struct stm32_qspi_regs {
|
|
u32 cr; /* 0x00 */
|
|
u32 dcr; /* 0x04 */
|
|
u32 sr; /* 0x08 */
|
|
u32 fcr; /* 0x0C */
|
|
u32 dlr; /* 0x10 */
|
|
u32 ccr; /* 0x14 */
|
|
u32 ar; /* 0x18 */
|
|
u32 abr; /* 0x1C */
|
|
u32 dr; /* 0x20 */
|
|
u32 psmkr; /* 0x24 */
|
|
u32 psmar; /* 0x28 */
|
|
u32 pir; /* 0x2C */
|
|
u32 lptr; /* 0x30 */
|
|
};
|
|
|
|
/*
|
|
* QUADSPI control register
|
|
*/
|
|
#define STM32_QSPI_CR_EN BIT(0)
|
|
#define STM32_QSPI_CR_ABORT BIT(1)
|
|
#define STM32_QSPI_CR_DMAEN BIT(2)
|
|
#define STM32_QSPI_CR_TCEN BIT(3)
|
|
#define STM32_QSPI_CR_SSHIFT BIT(4)
|
|
#define STM32_QSPI_CR_DFM BIT(6)
|
|
#define STM32_QSPI_CR_FSEL BIT(7)
|
|
#define STM32_QSPI_CR_FTHRES_SHIFT 8
|
|
#define STM32_QSPI_CR_TEIE BIT(16)
|
|
#define STM32_QSPI_CR_TCIE BIT(17)
|
|
#define STM32_QSPI_CR_FTIE BIT(18)
|
|
#define STM32_QSPI_CR_SMIE BIT(19)
|
|
#define STM32_QSPI_CR_TOIE BIT(20)
|
|
#define STM32_QSPI_CR_APMS BIT(22)
|
|
#define STM32_QSPI_CR_PMM BIT(23)
|
|
#define STM32_QSPI_CR_PRESCALER_MASK GENMASK(7, 0)
|
|
#define STM32_QSPI_CR_PRESCALER_SHIFT 24
|
|
|
|
/*
|
|
* QUADSPI device configuration register
|
|
*/
|
|
#define STM32_QSPI_DCR_CKMODE BIT(0)
|
|
#define STM32_QSPI_DCR_CSHT_MASK GENMASK(2, 0)
|
|
#define STM32_QSPI_DCR_CSHT_SHIFT 8
|
|
#define STM32_QSPI_DCR_FSIZE_MASK GENMASK(4, 0)
|
|
#define STM32_QSPI_DCR_FSIZE_SHIFT 16
|
|
|
|
/*
|
|
* QUADSPI status register
|
|
*/
|
|
#define STM32_QSPI_SR_TEF BIT(0)
|
|
#define STM32_QSPI_SR_TCF BIT(1)
|
|
#define STM32_QSPI_SR_FTF BIT(2)
|
|
#define STM32_QSPI_SR_SMF BIT(3)
|
|
#define STM32_QSPI_SR_TOF BIT(4)
|
|
#define STM32_QSPI_SR_BUSY BIT(5)
|
|
|
|
/*
|
|
* QUADSPI flag clear register
|
|
*/
|
|
#define STM32_QSPI_FCR_CTEF BIT(0)
|
|
#define STM32_QSPI_FCR_CTCF BIT(1)
|
|
#define STM32_QSPI_FCR_CSMF BIT(3)
|
|
#define STM32_QSPI_FCR_CTOF BIT(4)
|
|
|
|
/*
|
|
* QUADSPI communication configuration register
|
|
*/
|
|
#define STM32_QSPI_CCR_DDRM BIT(31)
|
|
#define STM32_QSPI_CCR_DHHC BIT(30)
|
|
#define STM32_QSPI_CCR_SIOO BIT(28)
|
|
#define STM32_QSPI_CCR_FMODE_SHIFT 26
|
|
#define STM32_QSPI_CCR_DMODE_SHIFT 24
|
|
#define STM32_QSPI_CCR_DCYC_SHIFT 18
|
|
#define STM32_QSPI_CCR_ABSIZE_SHIFT 16
|
|
#define STM32_QSPI_CCR_ABMODE_SHIFT 14
|
|
#define STM32_QSPI_CCR_ADSIZE_SHIFT 12
|
|
#define STM32_QSPI_CCR_ADMODE_SHIFT 10
|
|
#define STM32_QSPI_CCR_IMODE_SHIFT 8
|
|
|
|
#define STM32_QSPI_CCR_IND_WRITE 0
|
|
#define STM32_QSPI_CCR_IND_READ 1
|
|
#define STM32_QSPI_CCR_MEM_MAP 3
|
|
|
|
#define STM32_QSPI_MAX_MMAP_SZ SZ_256M
|
|
#define STM32_QSPI_MAX_CHIP 2
|
|
|
|
#define STM32_QSPI_FIFO_TIMEOUT_US 30000
|
|
#define STM32_QSPI_CMD_TIMEOUT_US 1000000
|
|
#define STM32_BUSY_TIMEOUT_US 100000
|
|
#define STM32_ABT_TIMEOUT_US 100000
|
|
|
|
struct stm32_qspi_priv {
|
|
struct stm32_qspi_regs *regs;
|
|
void __iomem *mm_base;
|
|
resource_size_t mm_size;
|
|
ulong clock_rate;
|
|
int cs_used;
|
|
};
|
|
|
|
static int _stm32_qspi_wait_for_not_busy(struct stm32_qspi_priv *priv)
|
|
{
|
|
u32 sr;
|
|
int ret;
|
|
|
|
ret = readl_poll_timeout(&priv->regs->sr, sr,
|
|
!(sr & STM32_QSPI_SR_BUSY),
|
|
STM32_BUSY_TIMEOUT_US);
|
|
if (ret)
|
|
log_err("busy timeout (stat:%#x)\n", sr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int _stm32_qspi_wait_cmd(struct stm32_qspi_priv *priv,
|
|
const struct spi_mem_op *op)
|
|
{
|
|
u32 sr;
|
|
int ret = 0;
|
|
|
|
ret = readl_poll_timeout(&priv->regs->sr, sr,
|
|
sr & STM32_QSPI_SR_TCF,
|
|
STM32_QSPI_CMD_TIMEOUT_US);
|
|
if (ret) {
|
|
log_err("cmd timeout (stat:%#x)\n", sr);
|
|
} else if (readl(&priv->regs->sr) & STM32_QSPI_SR_TEF) {
|
|
log_err("transfer error (stat:%#x)\n", sr);
|
|
ret = -EIO;
|
|
}
|
|
|
|
/* clear flags */
|
|
writel(STM32_QSPI_FCR_CTCF | STM32_QSPI_FCR_CTEF, &priv->regs->fcr);
|
|
|
|
if (!ret)
|
|
ret = _stm32_qspi_wait_for_not_busy(priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void _stm32_qspi_read_fifo(u8 *val, void __iomem *addr)
|
|
{
|
|
*val = readb(addr);
|
|
schedule();
|
|
}
|
|
|
|
static void _stm32_qspi_write_fifo(u8 *val, void __iomem *addr)
|
|
{
|
|
writeb(*val, addr);
|
|
}
|
|
|
|
static int _stm32_qspi_poll(struct stm32_qspi_priv *priv,
|
|
const struct spi_mem_op *op)
|
|
{
|
|
void (*fifo)(u8 *val, void __iomem *addr);
|
|
u32 len = op->data.nbytes, sr;
|
|
u8 *buf;
|
|
int ret;
|
|
|
|
if (op->data.dir == SPI_MEM_DATA_IN) {
|
|
fifo = _stm32_qspi_read_fifo;
|
|
buf = op->data.buf.in;
|
|
|
|
} else {
|
|
fifo = _stm32_qspi_write_fifo;
|
|
buf = (u8 *)op->data.buf.out;
|
|
}
|
|
|
|
while (len--) {
|
|
ret = readl_poll_timeout(&priv->regs->sr, sr,
|
|
sr & STM32_QSPI_SR_FTF,
|
|
STM32_QSPI_FIFO_TIMEOUT_US);
|
|
if (ret) {
|
|
log_err("fifo timeout (len:%d stat:%#x)\n", len, sr);
|
|
return ret;
|
|
}
|
|
|
|
fifo(buf++, &priv->regs->dr);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_qspi_mm(struct stm32_qspi_priv *priv,
|
|
const struct spi_mem_op *op)
|
|
{
|
|
memcpy_fromio(op->data.buf.in, priv->mm_base + op->addr.val,
|
|
op->data.nbytes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int _stm32_qspi_tx(struct stm32_qspi_priv *priv,
|
|
const struct spi_mem_op *op,
|
|
u8 mode)
|
|
{
|
|
if (!op->data.nbytes)
|
|
return 0;
|
|
|
|
if (mode == STM32_QSPI_CCR_MEM_MAP)
|
|
return stm32_qspi_mm(priv, op);
|
|
|
|
return _stm32_qspi_poll(priv, op);
|
|
}
|
|
|
|
static int _stm32_qspi_get_mode(u8 buswidth)
|
|
{
|
|
if (buswidth == 4)
|
|
return 3;
|
|
|
|
return buswidth;
|
|
}
|
|
|
|
static int stm32_qspi_exec_op(struct spi_slave *slave,
|
|
const struct spi_mem_op *op)
|
|
{
|
|
struct stm32_qspi_priv *priv = dev_get_priv(slave->dev->parent);
|
|
u32 cr, ccr, addr_max;
|
|
u8 mode = STM32_QSPI_CCR_IND_WRITE;
|
|
int timeout, ret;
|
|
|
|
dev_dbg(slave->dev, "cmd:%#x mode:%d.%d.%d.%d addr:%#llx len:%#x\n",
|
|
op->cmd.opcode, op->cmd.buswidth, op->addr.buswidth,
|
|
op->dummy.buswidth, op->data.buswidth,
|
|
op->addr.val, op->data.nbytes);
|
|
|
|
addr_max = op->addr.val + op->data.nbytes + 1;
|
|
|
|
if (op->data.dir == SPI_MEM_DATA_IN && op->data.nbytes) {
|
|
if (addr_max < priv->mm_size && op->addr.buswidth)
|
|
mode = STM32_QSPI_CCR_MEM_MAP;
|
|
else
|
|
mode = STM32_QSPI_CCR_IND_READ;
|
|
}
|
|
|
|
if (op->data.nbytes)
|
|
writel(op->data.nbytes - 1, &priv->regs->dlr);
|
|
|
|
ccr = (mode << STM32_QSPI_CCR_FMODE_SHIFT);
|
|
ccr |= op->cmd.opcode;
|
|
ccr |= (_stm32_qspi_get_mode(op->cmd.buswidth)
|
|
<< STM32_QSPI_CCR_IMODE_SHIFT);
|
|
|
|
if (op->addr.nbytes) {
|
|
ccr |= ((op->addr.nbytes - 1) << STM32_QSPI_CCR_ADSIZE_SHIFT);
|
|
ccr |= (_stm32_qspi_get_mode(op->addr.buswidth)
|
|
<< STM32_QSPI_CCR_ADMODE_SHIFT);
|
|
}
|
|
|
|
if (op->dummy.buswidth && op->dummy.nbytes)
|
|
ccr |= (op->dummy.nbytes * 8 / op->dummy.buswidth
|
|
<< STM32_QSPI_CCR_DCYC_SHIFT);
|
|
|
|
if (op->data.nbytes)
|
|
ccr |= (_stm32_qspi_get_mode(op->data.buswidth)
|
|
<< STM32_QSPI_CCR_DMODE_SHIFT);
|
|
|
|
writel(ccr, &priv->regs->ccr);
|
|
|
|
if (op->addr.nbytes && mode != STM32_QSPI_CCR_MEM_MAP)
|
|
writel(op->addr.val, &priv->regs->ar);
|
|
|
|
ret = _stm32_qspi_tx(priv, op, mode);
|
|
/*
|
|
* Abort in:
|
|
* -error case
|
|
* -read memory map: prefetching must be stopped if we read the last
|
|
* byte of device (device size - fifo size). like device size is not
|
|
* knows, the prefetching is always stop.
|
|
*/
|
|
if (ret || mode == STM32_QSPI_CCR_MEM_MAP)
|
|
goto abort;
|
|
|
|
/* Wait end of tx in indirect mode */
|
|
ret = _stm32_qspi_wait_cmd(priv, op);
|
|
if (ret)
|
|
goto abort;
|
|
|
|
return 0;
|
|
|
|
abort:
|
|
setbits_le32(&priv->regs->cr, STM32_QSPI_CR_ABORT);
|
|
|
|
/* Wait clear of abort bit by hw */
|
|
timeout = readl_poll_timeout(&priv->regs->cr, cr,
|
|
!(cr & STM32_QSPI_CR_ABORT),
|
|
STM32_ABT_TIMEOUT_US);
|
|
|
|
writel(STM32_QSPI_FCR_CTCF, &priv->regs->fcr);
|
|
|
|
if (ret || timeout)
|
|
dev_err(slave->dev, "ret:%d abort timeout:%d\n", ret, timeout);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int stm32_qspi_probe(struct udevice *bus)
|
|
{
|
|
struct stm32_qspi_priv *priv = dev_get_priv(bus);
|
|
struct resource res;
|
|
struct clk clk;
|
|
struct reset_ctl reset_ctl;
|
|
int ret;
|
|
|
|
ret = dev_read_resource_byname(bus, "qspi", &res);
|
|
if (ret) {
|
|
dev_err(bus, "can't get regs base addresses(ret = %d)!\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
priv->regs = (struct stm32_qspi_regs *)res.start;
|
|
|
|
ret = dev_read_resource_byname(bus, "qspi_mm", &res);
|
|
if (ret) {
|
|
dev_err(bus, "can't get mmap base address(ret = %d)!\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
priv->mm_base = (void __iomem *)res.start;
|
|
|
|
priv->mm_size = resource_size(&res);
|
|
if (priv->mm_size > STM32_QSPI_MAX_MMAP_SZ)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(bus, "regs=<0x%p> mapped=<0x%p> mapped_size=<0x%lx>\n",
|
|
priv->regs, priv->mm_base, priv->mm_size);
|
|
|
|
ret = clk_get_by_index(bus, 0, &clk);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = clk_enable(&clk);
|
|
if (ret) {
|
|
dev_err(bus, "failed to enable clock\n");
|
|
return ret;
|
|
}
|
|
|
|
priv->clock_rate = clk_get_rate(&clk);
|
|
if (!priv->clock_rate) {
|
|
clk_disable(&clk);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = reset_get_by_index(bus, 0, &reset_ctl);
|
|
if (ret) {
|
|
if (ret != -ENOENT) {
|
|
dev_err(bus, "failed to get reset\n");
|
|
clk_disable(&clk);
|
|
return ret;
|
|
}
|
|
} else {
|
|
/* Reset QSPI controller */
|
|
reset_assert(&reset_ctl);
|
|
udelay(2);
|
|
reset_deassert(&reset_ctl);
|
|
}
|
|
|
|
priv->cs_used = -1;
|
|
|
|
setbits_le32(&priv->regs->cr, STM32_QSPI_CR_SSHIFT);
|
|
|
|
/* Set dcr fsize to max address */
|
|
setbits_le32(&priv->regs->dcr,
|
|
STM32_QSPI_DCR_FSIZE_MASK << STM32_QSPI_DCR_FSIZE_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_qspi_claim_bus(struct udevice *dev)
|
|
{
|
|
struct stm32_qspi_priv *priv = dev_get_priv(dev->parent);
|
|
struct dm_spi_slave_plat *slave_plat = dev_get_parent_plat(dev);
|
|
int slave_cs = slave_plat->cs;
|
|
|
|
if (slave_cs >= STM32_QSPI_MAX_CHIP)
|
|
return -ENODEV;
|
|
|
|
if (priv->cs_used != slave_cs) {
|
|
priv->cs_used = slave_cs;
|
|
|
|
/* Set chip select */
|
|
clrsetbits_le32(&priv->regs->cr, STM32_QSPI_CR_FSEL,
|
|
priv->cs_used ? STM32_QSPI_CR_FSEL : 0);
|
|
}
|
|
|
|
setbits_le32(&priv->regs->cr, STM32_QSPI_CR_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_qspi_release_bus(struct udevice *dev)
|
|
{
|
|
struct stm32_qspi_priv *priv = dev_get_priv(dev->parent);
|
|
|
|
clrbits_le32(&priv->regs->cr, STM32_QSPI_CR_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_qspi_set_speed(struct udevice *bus, uint speed)
|
|
{
|
|
struct stm32_qspi_priv *priv = dev_get_priv(bus);
|
|
u32 qspi_clk = priv->clock_rate;
|
|
u32 prescaler = 255;
|
|
u32 csht;
|
|
int ret;
|
|
|
|
if (speed > 0) {
|
|
prescaler = 0;
|
|
if (qspi_clk) {
|
|
prescaler = DIV_ROUND_UP(qspi_clk, speed) - 1;
|
|
if (prescaler > 255)
|
|
prescaler = 255;
|
|
}
|
|
}
|
|
|
|
csht = DIV_ROUND_UP((5 * qspi_clk) / (prescaler + 1), 100000000);
|
|
csht = (csht - 1) & STM32_QSPI_DCR_CSHT_MASK;
|
|
|
|
ret = _stm32_qspi_wait_for_not_busy(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
clrsetbits_le32(&priv->regs->cr,
|
|
STM32_QSPI_CR_PRESCALER_MASK <<
|
|
STM32_QSPI_CR_PRESCALER_SHIFT,
|
|
prescaler << STM32_QSPI_CR_PRESCALER_SHIFT);
|
|
|
|
clrsetbits_le32(&priv->regs->dcr,
|
|
STM32_QSPI_DCR_CSHT_MASK << STM32_QSPI_DCR_CSHT_SHIFT,
|
|
csht << STM32_QSPI_DCR_CSHT_SHIFT);
|
|
|
|
dev_dbg(bus, "regs=%p, speed=%d\n", priv->regs,
|
|
(qspi_clk / (prescaler + 1)));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int stm32_qspi_set_mode(struct udevice *bus, uint mode)
|
|
{
|
|
struct stm32_qspi_priv *priv = dev_get_priv(bus);
|
|
int ret;
|
|
const char *str_rx, *str_tx;
|
|
|
|
ret = _stm32_qspi_wait_for_not_busy(priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((mode & SPI_CPHA) && (mode & SPI_CPOL))
|
|
setbits_le32(&priv->regs->dcr, STM32_QSPI_DCR_CKMODE);
|
|
else if (!(mode & SPI_CPHA) && !(mode & SPI_CPOL))
|
|
clrbits_le32(&priv->regs->dcr, STM32_QSPI_DCR_CKMODE);
|
|
else
|
|
return -ENODEV;
|
|
|
|
if (mode & SPI_CS_HIGH)
|
|
return -ENODEV;
|
|
|
|
if (mode & SPI_RX_QUAD)
|
|
str_rx = "quad";
|
|
else if (mode & SPI_RX_DUAL)
|
|
str_rx = "dual";
|
|
else
|
|
str_rx = "single";
|
|
|
|
if (mode & SPI_TX_QUAD)
|
|
str_tx = "quad";
|
|
else if (mode & SPI_TX_DUAL)
|
|
str_tx = "dual";
|
|
else
|
|
str_tx = "single";
|
|
|
|
dev_dbg(bus, "regs=%p, mode=%d rx: %s, tx: %s\n",
|
|
priv->regs, mode, str_rx, str_tx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct spi_controller_mem_ops stm32_qspi_mem_ops = {
|
|
.exec_op = stm32_qspi_exec_op,
|
|
};
|
|
|
|
static const struct dm_spi_ops stm32_qspi_ops = {
|
|
.claim_bus = stm32_qspi_claim_bus,
|
|
.release_bus = stm32_qspi_release_bus,
|
|
.set_speed = stm32_qspi_set_speed,
|
|
.set_mode = stm32_qspi_set_mode,
|
|
.mem_ops = &stm32_qspi_mem_ops,
|
|
};
|
|
|
|
static const struct udevice_id stm32_qspi_ids[] = {
|
|
{ .compatible = "st,stm32f469-qspi" },
|
|
{ }
|
|
};
|
|
|
|
U_BOOT_DRIVER(stm32_qspi) = {
|
|
.name = "stm32_qspi",
|
|
.id = UCLASS_SPI,
|
|
.of_match = stm32_qspi_ids,
|
|
.ops = &stm32_qspi_ops,
|
|
.priv_auto = sizeof(struct stm32_qspi_priv),
|
|
.probe = stm32_qspi_probe,
|
|
};
|