git: 94e25b7acba7 - main - flex_spi: Support for FlexSPI Flash controller.
Date: Tue, 30 Nov 2021 05:42:10 UTC
The branch main has been updated by wma: URL: https://cgit.FreeBSD.org/src/commit/?id=94e25b7acba7f929606a1a57f8d193995d8f8920 commit 94e25b7acba7f929606a1a57f8d193995d8f8920 Author: Wojciech Macek <wma@FreeBSD.org> AuthorDate: 2021-11-25 09:36:55 +0000 Commit: Wojciech Macek <wma@FreeBSD.org> CommitDate: 2021-11-30 05:41:34 +0000 flex_spi: Support for FlexSPI Flash controller. NXP FlexSPI is a complex SPI controller which provides full offload for accessing NOR Flash. Create a Flash driver which attaches to existing FreeBSD infrastructure and exports generic READ and WRITE disk commands. The Flash has to be identified first to configure controller internals. For now, only one NOR Flash chip is supported. Future commits shall either increase number of known chips or implement SFDP mechanism which can be used by other Flash drivers. Sponsored by: Alstom Obtained from: Semihalf Differential revision: https://reviews.freebsd.org/D33117 --- sys/conf/files.arm64 | 1 + sys/dev/flash/flexspi/flex_spi.c | 991 +++++++++++++++++++++++++++++++++++++ sys/dev/flash/flexspi/flex_spi.h | 336 +++++++++++++ sys/modules/Makefile | 1 + sys/modules/flash/Makefile | 10 + sys/modules/flash/flexspi/Makefile | 15 + 6 files changed, 1354 insertions(+) diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64 index 722a33da812d..f941764ebd0f 100644 --- a/sys/conf/files.arm64 +++ b/sys/conf/files.arm64 @@ -529,6 +529,7 @@ arm64/qoriq/clk/lx2160a_clkgen.c optional clk SOC_NXP_LS arm64/qoriq/clk/qoriq_clk_pll.c optional clk SOC_NXP_LS arm64/qoriq/clk/qoriq_clkgen.c optional clk SOC_NXP_LS dev/ahci/ahci_fsl_fdt.c optional SOC_NXP_LS ahci fdt +dev/flash/flexspi/flex_spi.c optional clk flex_spi SOC_NXP_LS fdt # Qualcomm arm64/qualcomm/qcom_gcc.c optional qcom_gcc fdt diff --git a/sys/dev/flash/flexspi/flex_spi.c b/sys/dev/flash/flexspi/flex_spi.c new file mode 100644 index 000000000000..fc85919fc2f8 --- /dev/null +++ b/sys/dev/flash/flexspi/flex_spi.c @@ -0,0 +1,991 @@ +/*- + * Copyright (c) 2021 Alstom Group. + * Copyright (c) 2021 Semihalf. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include "opt_platform.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bio.h> +#include <sys/endian.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/rman.h> + +#include <geom/geom_disk.h> + +#include <machine/bus.h> + +#include <dev/extres/clk/clk.h> +#include <dev/fdt/fdt_common.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <vm/pmap.h> + +#include "flex_spi.h" + +static MALLOC_DEFINE(SECTOR_BUFFER, "flex_spi", "FSL QSPI sector buffer memory"); + +#define AHB_LUT_ID 31 +#define MHZ(x) ((x)*1000*1000) +#define SPI_DEFAULT_CLK_RATE (MHZ(10)) + +static int driver_flags = 0; +SYSCTL_NODE(_hw, OID_AUTO, flex_spi, CTLFLAG_RD | CTLFLAG_MPSAFE, 0, + "FlexSPI driver parameters"); +SYSCTL_INT(_hw_flex_spi, OID_AUTO, driver_flags, CTLFLAG_RDTUN, &driver_flags, 0, + "Configuration flags and quirks"); + +static struct ofw_compat_data flex_spi_compat_data[] = { + {"nxp,lx2160a-fspi", true}, + {NULL, false} +}; + +struct flex_spi_flash_info { + char* name; + uint32_t jedecid; + uint32_t sectorsize; + uint32_t sectorcount; + uint32_t erasesize; + uint32_t maxclk; +}; + +/* Add information about supported Flashes. TODO: use SFDP instead */ +static struct flex_spi_flash_info flex_spi_flash_info[] = { + {"W25Q128JW", 0x001860ef, 64*1024, 256, 4096, MHZ(100)}, + {NULL, 0, 0, 0, 0, 0} +}; + +struct flex_spi_softc +{ + device_t dev; + unsigned int flags; + + struct bio_queue_head bio_queue; + struct mtx disk_mtx; + struct disk *disk; + struct proc *p; + unsigned int taskstate; + uint8_t *buf; + + struct resource *ahb_mem_res; + struct resource *mem_res; + + clk_t fspi_clk_en; + clk_t fspi_clk; + uint64_t fspi_clk_en_hz; + uint64_t fspi_clk_hz; + + /* TODO: support more than one Flash per bus */ + uint64_t fspi_max_clk; + uint32_t quirks; + + /* Flash parameters */ + uint32_t sectorsize; + uint32_t sectorcount; + uint32_t erasesize; +}; + +static int flex_spi_read(struct flex_spi_softc *sc, off_t offset, caddr_t data, + size_t count); +static int flex_spi_write(struct flex_spi_softc *sc, off_t offset, + uint8_t *data, size_t size); + +static int flex_spi_attach(device_t dev); +static int flex_spi_probe(device_t dev); +static int flex_spi_detach(device_t dev); + +/* disk routines */ +static int flex_spi_open(struct disk *dp); +static int flex_spi_close(struct disk *dp); +static int flex_spi_ioctl(struct disk *, u_long, void *, int, struct thread *); +static void flex_spi_strategy(struct bio *bp); +static int flex_spi_getattr(struct bio *bp); +static void flex_spi_task(void *arg); + +static uint32_t +read_reg(struct flex_spi_softc *sc, uint32_t offset) +{ + + return ((bus_read_4(sc->mem_res, offset))); +} + +static void +write_reg(struct flex_spi_softc *sc, uint32_t offset, uint32_t value) +{ + + bus_write_4(sc->mem_res, offset, (value)); +} + +static int +reg_read_poll_tout(struct flex_spi_softc *sc, uint32_t offset, uint32_t mask, + uint32_t delay_us, uint32_t iterations, bool positive) +{ + uint32_t reg; + uint32_t condition = 0; + + do { + reg = read_reg(sc, offset); + if (positive) + condition = ((reg & mask) == 0); + else + condition = ((reg & mask) != 0); + + if (condition == 0) + break; + + DELAY(delay_us); + } while (condition && (--iterations > 0)); + + return (condition != 0); +} + +static int +flex_spi_clk_setup(struct flex_spi_softc *sc, uint32_t rate) +{ + int ret = 0; + + /* disable to avoid glitching */ + ret |= clk_disable(sc->fspi_clk_en); + ret |= clk_disable(sc->fspi_clk); + + ret |= clk_set_freq(sc->fspi_clk, rate, 0); + sc->fspi_clk_hz = rate; + + /* enable clocks back */ + ret |= clk_enable(sc->fspi_clk_en); + ret |= clk_enable(sc->fspi_clk); + + if (ret) + return (EINVAL); + + return (0); +} + +static void +flex_spi_prepare_lut(struct flex_spi_softc *sc, uint8_t op) +{ + uint32_t lut_id; + uint32_t lut; + + /* unlock LUT */ + write_reg(sc, FSPI_LUTKEY, FSPI_LUTKEY_VALUE); + write_reg(sc, FSPI_LCKCR, FSPI_LCKER_UNLOCK); + + /* Read JEDEC ID */ + lut_id = 0; + + switch (op) { + case LUT_FLASH_CMD_JEDECID: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_READ_IDENT); + lut |= LUT_DEF(1, LUT_NXP_READ, LUT_PAD(1), 0); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, 0); + break; + case LUT_FLASH_CMD_READ: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_FAST_READ); + lut |= LUT_DEF(1, LUT_ADDR, LUT_PAD(1), 3*8); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + lut = LUT_DEF(0, LUT_DUMMY, LUT_PAD(1), 1*8); + lut |= LUT_DEF(1, LUT_NXP_READ, LUT_PAD(1), 0); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 8, 0); + break; + case LUT_FLASH_CMD_STATUS_READ: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_READ_STATUS); + lut |= LUT_DEF(1, LUT_NXP_READ, LUT_PAD(1), 0); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, 0); + break; + case LUT_FLASH_CMD_PAGE_PROGRAM: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_PAGE_PROGRAM); + lut |= LUT_DEF(1, LUT_ADDR, LUT_PAD(1), 3*8); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + lut = LUT_DEF(0, LUT_NXP_WRITE, LUT_PAD(1), 0); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 8, 0); + break; + case LUT_FLASH_CMD_WRITE_ENABLE: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_WRITE_ENABLE); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, 0); + break; + case LUT_FLASH_CMD_WRITE_DISABLE: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_WRITE_DISABLE); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, 0); + break; + case LUT_FLASH_CMD_SECTOR_ERASE: + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_SECTOR_ERASE); + lut |= LUT_DEF(1, LUT_ADDR, LUT_PAD(1), 3*8); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, 0); + break; + default: + write_reg(sc, FSPI_LUT_REG(lut_id), 0); + } + + /* lock LUT */ + write_reg(sc, FSPI_LUTKEY, FSPI_LUTKEY_VALUE); + write_reg(sc, FSPI_LCKCR, FSPI_LCKER_LOCK); +} + +static void +flex_spi_prepare_ahb_lut(struct flex_spi_softc *sc) +{ + uint32_t lut_id; + uint32_t lut; + + /* unlock LUT */ + write_reg(sc, FSPI_LUTKEY, FSPI_LUTKEY_VALUE); + write_reg(sc, FSPI_LCKCR, FSPI_LCKER_UNLOCK); + + lut_id = AHB_LUT_ID; + lut = LUT_DEF(0, LUT_CMD, LUT_PAD(1), FSPI_CMD_FAST_READ); + lut |= LUT_DEF(1, LUT_ADDR, LUT_PAD(1), 3*8); + write_reg(sc, FSPI_LUT_REG(lut_id), lut); + lut = LUT_DEF(0, LUT_DUMMY, LUT_PAD(1), 1*8); + lut |= LUT_DEF(1, LUT_NXP_READ, LUT_PAD(1), 0); + write_reg(sc, FSPI_LUT_REG(lut_id) + 4, lut); + write_reg(sc, FSPI_LUT_REG(lut_id) + 8, 0); + + /* lock LUT */ + write_reg(sc, FSPI_LUTKEY, FSPI_LUTKEY_VALUE); + write_reg(sc, FSPI_LCKCR, FSPI_LCKER_LOCK); +} + +#define DIR_READ 0 +#define DIR_WRITE 1 + +static void +flex_spi_read_rxfifo(struct flex_spi_softc *sc, uint8_t *buf, uint8_t size) +{ + int i, ret, reg; + + /* + * Default value of water mark level is 8 bytes, hence in single + * read request controller can read max 8 bytes of data. + */ + for (i = 0; i < size; i += 4) { + /* Wait for RXFIFO available */ + if (i % 8 == 0) { + ret = reg_read_poll_tout(sc, FSPI_INTR, FSPI_INTR_IPRXWA, + 1, 50000, 1); + if (ret) + device_printf(sc->dev, + "timed out waiting for FSPI_INTR_IPRXWA\n"); + } + + if (i % 8 == 0) + reg = read_reg(sc, FSPI_RFDR); + else + reg = read_reg(sc, FSPI_RFDR + 4); + + if (size >= (i + 4)) + *(uint32_t *)(buf + i) = reg; + else + memcpy(buf + i, ®, size - i); + + /* move the FIFO pointer */ + if (i % 8 != 0) + write_reg(sc, FSPI_INTR, FSPI_INTR_IPRXWA); + } + + /* invalid the RXFIFO */ + write_reg(sc, FSPI_IPRXFCR, FSPI_IPRXFCR_CLR); + /* move the FIFO pointer */ + write_reg(sc, FSPI_INTR, FSPI_INTR_IPRXWA); +} + +static void +flex_spi_write_txfifo(struct flex_spi_softc *sc, uint8_t *buf, uint8_t size) +{ + int i, ret, reg; + + /* invalid the TXFIFO */ + write_reg(sc, FSPI_IPRXFCR, FSPI_IPTXFCR_CLR); + + /* + * Default value of water mark level is 8 bytes, hence in single + * read request controller can read max 8 bytes of data. + */ + for (i = 0; i < size; i += 4) { + /* Wait for RXFIFO available */ + if (i % 8 == 0) { + ret = reg_read_poll_tout(sc, FSPI_INTR, FSPI_INTR_IPTXWE, + 1, 50000, 1); + if (ret) + device_printf(sc->dev, + "timed out waiting for FSPI_INTR_IPRXWA\n"); + } + + if (size >= (i + 4)) + reg = *(uint32_t *)(buf + i); + else { + reg = 0; + memcpy(®, buf + i, size - i); + } + + if (i % 8 == 0) + write_reg(sc, FSPI_TFDR, reg); + else + write_reg(sc, FSPI_TFDR + 4, reg); + + /* move the FIFO pointer */ + if (i % 8 != 0) + write_reg(sc, FSPI_INTR, FSPI_INTR_IPTXWE); + } + + /* move the FIFO pointer */ + write_reg(sc, FSPI_INTR, FSPI_INTR_IPTXWE); +} + +static int +flex_spi_do_op(struct flex_spi_softc *sc, uint32_t op, uint32_t addr, + uint8_t *buf, uint8_t size, uint8_t dir) +{ + + uint32_t cnt = 1000, reg; + + reg = read_reg(sc, FSPI_IPRXFCR); + /* invalidate RXFIFO first */ + reg &= ~FSPI_IPRXFCR_DMA_EN; + reg |= FSPI_IPRXFCR_CLR; + write_reg(sc, FSPI_IPRXFCR, reg); + + /* Prepare LUT */ + flex_spi_prepare_lut(sc, op); + + write_reg(sc, FSPI_IPCR0, addr); + /* + * Always start the sequence at the same index since we update + * the LUT at each BIO operation. And also specify the DATA + * length, since it's has not been specified in the LUT. + */ + write_reg(sc, FSPI_IPCR1, size | + (0 << FSPI_IPCR1_SEQID_SHIFT) | (0 << FSPI_IPCR1_SEQNUM_SHIFT)); + + if ((size != 0) && (dir == DIR_WRITE)) + flex_spi_write_txfifo(sc, buf, size); + + /* Trigger the LUT now. */ + write_reg(sc, FSPI_IPCMD, FSPI_IPCMD_TRG); + + + /* Wait for completion. */ + do { + reg = read_reg(sc, FSPI_INTR); + if (reg & FSPI_INTR_IPCMDDONE) { + write_reg(sc, FSPI_INTR, FSPI_INTR_IPCMDDONE); + break; + } + DELAY(1); + } while (--cnt); + if (cnt == 0) { + device_printf(sc->dev, "timed out waiting for command completion\n"); + return (ETIMEDOUT); + } + + /* Invoke IP data read, if request is of data read. */ + if ((size != 0) && (dir == DIR_READ)) + flex_spi_read_rxfifo(sc, buf, size); + + return (0); +} + +static int +flex_spi_wait_for_controller(struct flex_spi_softc *sc) +{ + int err; + + /* Wait for controller being ready. */ + err = reg_read_poll_tout(sc, FSPI_STS0, + FSPI_STS0_ARB_IDLE, 1, POLL_TOUT, 1); + + return (err); +} + +static int +flex_spi_wait_for_flash(struct flex_spi_softc *sc) +{ + int ret; + uint32_t status = 0; + + ret = flex_spi_wait_for_controller(sc); + if (ret != 0) { + device_printf(sc->dev, "%s: timed out waiting for controller", __func__); + return (ret); + } + + do { + ret = flex_spi_do_op(sc, LUT_FLASH_CMD_STATUS_READ, 0, (void*)&status, + 1, DIR_READ); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to get flash status\n"); + return (ret); + } + + } while (status & STATUS_WIP); + + return (0); +} + +static int +flex_spi_identify(struct flex_spi_softc *sc) +{ + int ret; + uint32_t id = 0; + struct flex_spi_flash_info *finfo = flex_spi_flash_info; + + ret = flex_spi_do_op(sc, LUT_FLASH_CMD_JEDECID, 0, (void*)&id, sizeof(id), DIR_READ); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to identify device\n"); + return (ret); + } + + /* XXX TODO: SFDP to be implemented */ + while (finfo->jedecid != 0) { + if (id == finfo->jedecid) { + device_printf(sc->dev, "found %s Flash\n", finfo->name); + sc->sectorsize = finfo->sectorsize; + sc->sectorcount = finfo->sectorcount; + sc->erasesize = finfo->erasesize; + sc->fspi_max_clk = finfo->maxclk; + return (0); + } + finfo++; + } + + return (EINVAL); +} + +static inline int +flex_spi_force_ip_mode(struct flex_spi_softc *sc) +{ + + if (sc->quirks & FSPI_QUIRK_USE_IP_ONLY) + return (1); + if (driver_flags & FSPI_QUIRK_USE_IP_ONLY) + return (1); + + return (0); +} + +static int +flex_spi_read(struct flex_spi_softc *sc, off_t offset, caddr_t data, + size_t count) +{ + int err; + size_t len; + + /* Wait for controller being ready. */ + err = flex_spi_wait_for_controller(sc); + if (err) + device_printf(sc->dev, + "warning: spi_read, timed out waiting for controller"); + + /* Use AHB access whenever we can */ + if (flex_spi_force_ip_mode(sc) != 0) { + do { + if (((offset % 4) != 0) || (count < 4)) { + *(uint8_t*)data = bus_read_1(sc->ahb_mem_res, offset); + data++; + count--; + offset++; + } else { + *(uint32_t*)data = bus_read_4(sc->ahb_mem_res, offset); + data += 4; + count -= 4; + offset += 4; + } + } while (count); + + return (0); + } + + do { + len = min(64, count); + err = flex_spi_do_op(sc, LUT_FLASH_CMD_READ, offset, (void*)data, + len, DIR_READ); + if (err) + return (err); + offset += len; + data += len; + count -= len; + } while (count); + + return (0); +} + +static int +flex_spi_write(struct flex_spi_softc *sc, off_t offset, uint8_t *data, + size_t size) +{ + int ret = 0; + size_t ptr; + + flex_spi_wait_for_flash(sc); + ret = flex_spi_do_op(sc, LUT_FLASH_CMD_WRITE_ENABLE, offset, NULL, + 0, DIR_READ); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to enable writes\n"); + return (ret); + } + flex_spi_wait_for_flash(sc); + + /* per-sector write */ + while (size > 0) { + uint32_t sector_base = rounddown2(offset, sc->erasesize); + size_t size_in_sector = size; + + if (size_in_sector + offset > sector_base + sc->erasesize) + size_in_sector = sector_base + sc->erasesize - offset; + + /* Read sector */ + ret = flex_spi_read(sc, sector_base, sc->buf, sc->erasesize); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to read sector %d\n", + sector_base); + goto exit; + } + + /* Erase sector */ + flex_spi_wait_for_flash(sc); + ret = flex_spi_do_op(sc, LUT_FLASH_CMD_SECTOR_ERASE, offset, NULL, + 0, DIR_READ); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to erase sector %d\n", + sector_base); + goto exit; + } + + /* Update buffer with input data */ + memcpy(sc->buf + (offset - sector_base), data, size_in_sector); + + /* Write buffer back to the flash + * Up to 32 bytes per single request, request cannot spread + * across 256-byte page boundary + */ + for (ptr = 0; ptr < sc->erasesize; ptr += 32) { + flex_spi_wait_for_flash(sc); + ret = flex_spi_do_op(sc, LUT_FLASH_CMD_PAGE_PROGRAM, + sector_base + ptr, (void*)(sc->buf + ptr), 32, DIR_WRITE); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to write address %ld\n", + sector_base + ptr); + goto exit; + } + } + + /* update pointers */ + size = size - size_in_sector; + offset = offset + size; + } + + flex_spi_wait_for_flash(sc); + ret = flex_spi_do_op(sc, LUT_FLASH_CMD_WRITE_DISABLE, offset, (void*)sc->buf, + 0, DIR_READ); + if (ret != 0) { + device_printf(sc->dev, "ERROR: failed to disable writes\n"); + goto exit; + } + flex_spi_wait_for_flash(sc); + +exit: + + return (ret); +} + +static int +flex_spi_default_setup(struct flex_spi_softc *sc) +{ + int ret, i; + uint32_t reg; + + /* Default clock speed */ + ret = flex_spi_clk_setup(sc, SPI_DEFAULT_CLK_RATE); + if (ret) + return (ret); + + /* Reset the module */ + /* w1c register, wait unit clear */ + reg = read_reg(sc, FSPI_MCR0); + reg |= FSPI_MCR0_SWRST; + write_reg(sc, FSPI_MCR0, reg); + ret = reg_read_poll_tout(sc, FSPI_MCR0, FSPI_MCR0_SWRST, 1000, POLL_TOUT, 0); + if (ret != 0) { + device_printf(sc->dev, "time out waiting for reset"); + return (ret); + } + + /* Disable the module */ + write_reg(sc, FSPI_MCR0, FSPI_MCR0_MDIS); + + /* Reset the DLL register to default value */ + write_reg(sc, FSPI_DLLACR, FSPI_DLLACR_OVRDEN); + write_reg(sc, FSPI_DLLBCR, FSPI_DLLBCR_OVRDEN); + + /* enable module */ + write_reg(sc, FSPI_MCR0, FSPI_MCR0_AHB_TIMEOUT(0xFF) | + FSPI_MCR0_IP_TIMEOUT(0xFF) | (uint32_t) FSPI_MCR0_OCTCOMB_EN); + + /* + * Disable same device enable bit and configure all slave devices + * independently. + */ + reg = read_reg(sc, FSPI_MCR2); + reg = reg & ~(FSPI_MCR2_SAMEDEVICEEN); + write_reg(sc, FSPI_MCR2, reg); + + /* AHB configuration for access buffer 0~7. */ + for (i = 0; i < 7; i++) + write_reg(sc, FSPI_AHBRX_BUF0CR0 + 4 * i, 0); + + /* + * Set ADATSZ with the maximum AHB buffer size to improve the read + * performance. + */ + write_reg(sc, FSPI_AHBRX_BUF7CR0, (2048 / 8 | + FSPI_AHBRXBUF0CR7_PREF)); + + /* prefetch and no start address alignment limitation */ + write_reg(sc, FSPI_AHBCR, FSPI_AHBCR_PREF_EN | FSPI_AHBCR_RDADDROPT); + + /* AHB Read - Set lut sequence ID for all CS. */ + flex_spi_prepare_ahb_lut(sc); + write_reg(sc, FSPI_FLSHA1CR2, AHB_LUT_ID); + write_reg(sc, FSPI_FLSHA2CR2, AHB_LUT_ID); + write_reg(sc, FSPI_FLSHB1CR2, AHB_LUT_ID); + write_reg(sc, FSPI_FLSHB2CR2, AHB_LUT_ID); + + /* disable interrupts */ + write_reg(sc, FSPI_INTEN, 0); + + return (0); +} + +static int +flex_spi_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, flex_spi_compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "NXP FlexSPI Flash"); + return (BUS_PROBE_SPECIFIC); +} + +static int +flex_spi_attach(device_t dev) +{ + struct flex_spi_softc *sc; + phandle_t node; + int rid; + uint32_t reg; + + node = ofw_bus_get_node(dev); + sc = device_get_softc(dev); + sc->dev = dev; + + mtx_init(&sc->disk_mtx, "flex_spi_DISK", "QSPI disk mtx", MTX_DEF); + + /* Get memory resources. */ + rid = 0; + sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + + rid = 1; + sc->ahb_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE | RF_SHAREABLE); + + if (sc->mem_res == NULL || sc->ahb_mem_res == NULL) { + device_printf(dev, "could not allocate resources\n"); + flex_spi_detach(dev); + return (ENOMEM); + } + + /* Get clocks */ + if ((clk_get_by_ofw_name(dev, node, "fspi_en", &sc->fspi_clk_en) != 0) + || (clk_get_freq(sc->fspi_clk_en, &sc->fspi_clk_en_hz) != 0)) { + device_printf(dev, "could not get fspi_en clock\n"); + flex_spi_detach(dev); + return (EINVAL); + } + if ((clk_get_by_ofw_name(dev, node, "fspi", &sc->fspi_clk) != 0) + || (clk_get_freq(sc->fspi_clk, &sc->fspi_clk_hz) != 0)) { + device_printf(dev, "could not get fspi clock\n"); + flex_spi_detach(dev); + return (EINVAL); + } + + /* Enable clocks */ + if (clk_enable(sc->fspi_clk_en) != 0 || + clk_enable(sc->fspi_clk) != 0) { + device_printf(dev, "could not enable clocks\n"); + flex_spi_detach(dev); + return (EINVAL); + } + + /* Clear potential interrupts */ + reg = read_reg(sc, FSPI_INTR); + if (reg) + write_reg(sc, FSPI_INTR, reg); + + /* Default setup */ + if (flex_spi_default_setup(sc) != 0) { + device_printf(sc->dev, "Unable to initialize defaults\n"); + flex_spi_detach(dev); + return (ENXIO); + } + + /* Identify attached Flash */ + if(flex_spi_identify(sc) != 0) { + device_printf(sc->dev, "Unable to identify Flash\n"); + flex_spi_detach(dev); + return (ENXIO); + } + + if (flex_spi_clk_setup(sc, sc->fspi_max_clk) != 0) { + device_printf(sc->dev, "Unable to set up SPI max clock\n"); + flex_spi_detach(dev); + return (ENXIO); + } + + sc->buf = malloc(sc->erasesize, SECTOR_BUFFER, M_WAITOK); + if (sc->buf == NULL) { + device_printf(sc->dev, "Unable to set up allocate internal buffer\n"); + flex_spi_detach(dev); + return (ENOMEM); + } + + /* Move it to per-flash */ + sc->disk = disk_alloc(); + sc->disk->d_open = flex_spi_open; + sc->disk->d_close = flex_spi_close; + sc->disk->d_strategy = flex_spi_strategy; + sc->disk->d_getattr = flex_spi_getattr; + sc->disk->d_ioctl = flex_spi_ioctl; + sc->disk->d_name = "flash/qspi"; + sc->disk->d_drv1 = sc; + /* the most that can fit in a single spi transaction */ + sc->disk->d_maxsize = DFLTPHYS; + sc->disk->d_sectorsize = FLASH_SECTORSIZE; + sc->disk->d_unit = device_get_unit(sc->dev); + sc->disk->d_dump = NULL; + + sc->disk->d_mediasize = sc->sectorsize * sc->sectorcount; + sc->disk->d_stripesize = sc->erasesize; + + bioq_init(&sc->bio_queue); + sc->taskstate = TSTATE_RUNNING; + kproc_create(&flex_spi_task, sc, &sc->p, 0, 0, "task: qspi flash"); + disk_create(sc->disk, DISK_VERSION); + + return (0); +} + +static int +flex_spi_detach(device_t dev) +{ + struct flex_spi_softc *sc; + int err; + + sc = device_get_softc(dev); + err = 0; + + mtx_lock(&sc->disk_mtx); + if (sc->taskstate == TSTATE_RUNNING) { + sc->taskstate = TSTATE_STOPPING; + wakeup(sc->disk); + while (err == 0 && sc->taskstate != TSTATE_STOPPED) { + err = mtx_sleep(sc->disk, &sc->disk_mtx, 0, "flex_spi", + hz * 3); + if (err != 0) { + sc->taskstate = TSTATE_RUNNING; + device_printf(sc->dev, + "Failed to stop queue task\n"); + } + } + } + + mtx_unlock(&sc->disk_mtx); + mtx_destroy(&sc->disk_mtx); + + if (err == 0 && sc->taskstate == TSTATE_STOPPED) { + disk_destroy(sc->disk); + bioq_flush(&sc->bio_queue, NULL, ENXIO); + } + + /* Disable hardware. */ + + /* Release memory resource. */ + if (sc->mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + rman_get_rid(sc->mem_res), sc->mem_res); + + if (sc->ahb_mem_res != NULL) + bus_release_resource(dev, SYS_RES_MEMORY, + rman_get_rid(sc->ahb_mem_res), sc->ahb_mem_res); + + /* Disable clocks */ + if (sc->fspi_clk_en_hz) + clk_disable(sc->fspi_clk_en); + if (sc->fspi_clk_hz) + clk_disable(sc->fspi_clk); + + free(sc->buf, SECTOR_BUFFER); + + return (err); +} + +static int +flex_spi_open(struct disk *dp) +{ + + return (0); +} + +static int +flex_spi_close(struct disk *dp) +{ + + return (0); +} + +static int +flex_spi_ioctl(struct disk *dp, u_long cmd, void *data, int fflag, + struct thread *td) +{ + + return (ENOTSUP); +} + +static void +flex_spi_strategy(struct bio *bp) +{ + struct flex_spi_softc *sc; + + sc = (struct flex_spi_softc *)bp->bio_disk->d_drv1; + mtx_lock(&sc->disk_mtx); + bioq_disksort(&sc->bio_queue, bp); + mtx_unlock(&sc->disk_mtx); + wakeup(sc->disk); +} + +static int +flex_spi_getattr(struct bio *bp) +{ + struct flex_spi_softc *sc; + device_t dev; + + if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL) { + return (ENXIO); + } + + sc = bp->bio_disk->d_drv1; + dev = sc->dev; + + if (strcmp(bp->bio_attribute, "SPI::device") != 0) { + return (-1); + } + + if (bp->bio_length != sizeof(dev)) { + return (EFAULT); + } + + bcopy(&dev, bp->bio_data, sizeof(dev)); + + return (0); +} + +static void +flex_spi_task(void *arg) +{ + struct flex_spi_softc *sc; *** 447 LINES SKIPPED ***