diff options
Diffstat (limited to 'roms/u-boot/drivers/spi/stm32_qspi.c')
-rw-r--r-- | roms/u-boot/drivers/spi/stm32_qspi.c | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/spi/stm32_qspi.c b/roms/u-boot/drivers/spi/stm32_qspi.c new file mode 100644 index 000000000..4acc9047b --- /dev/null +++ b/roms/u-boot/drivers/spi/stm32_qspi.c @@ -0,0 +1,552 @@ +// 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/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_flash { + u32 cr; + u32 dcr; + bool initialized; +}; + +struct stm32_qspi_priv { + struct stm32_qspi_regs *regs; + struct stm32_qspi_flash flash[STM32_QSPI_MAX_CHIP]; + 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; + + if (!op->data.nbytes) + return _stm32_qspi_wait_for_not_busy(priv); + + 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); + + return ret; +} + +static void _stm32_qspi_read_fifo(u8 *val, void __iomem *addr) +{ + *val = readb(addr); + WATCHDOG_RESET(); +} + +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); + + ret = _stm32_qspi_wait_for_not_busy(priv); + if (ret) + return ret; + + 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) { + struct stm32_qspi_flash *flash = &priv->flash[slave_cs]; + + priv->cs_used = slave_cs; + + if (flash->initialized) { + /* Set the configuration: speed + cs */ + writel(flash->cr, &priv->regs->cr); + writel(flash->dcr, &priv->regs->dcr); + } else { + /* Set chip select */ + clrsetbits_le32(&priv->regs->cr, STM32_QSPI_CR_FSEL, + priv->cs_used ? STM32_QSPI_CR_FSEL : 0); + + /* Save the configuration: speed + cs */ + flash->cr = readl(&priv->regs->cr); + flash->dcr = readl(&priv->regs->dcr); + + flash->initialized = true; + } + } + + 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, +}; |