/*- * 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 #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); /* 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; if (!device_is_attached(dev)) goto free_resources; 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. */ free_resources: /* 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; struct bio *bp; sc = (struct flex_spi_softc *)arg; for (;;) { mtx_lock(&sc->disk_mtx); do { if (sc->taskstate == TSTATE_STOPPING) { sc->taskstate = TSTATE_STOPPED; mtx_unlock(&sc->disk_mtx); wakeup(sc->disk); kproc_exit(0); } bp = bioq_first(&sc->bio_queue); if (bp == NULL) mtx_sleep(sc->disk, &sc->disk_mtx, PRIBIO, "flex_spi", 0); } while (bp == NULL); bioq_remove(&sc->bio_queue, bp); mtx_unlock(&sc->disk_mtx); switch (bp->bio_cmd) { case BIO_READ: bp->bio_error = flex_spi_read(sc, bp->bio_offset, bp->bio_data, bp->bio_bcount); break; case BIO_WRITE: bp->bio_error = flex_spi_write(sc, bp->bio_offset, bp->bio_data, bp->bio_bcount); break; default: bp->bio_error = EINVAL; } biodone(bp); } } static device_method_t flex_spi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, flex_spi_probe), DEVMETHOD(device_attach, flex_spi_attach), DEVMETHOD(device_detach, flex_spi_detach), { 0, 0 } }; static driver_t flex_spi_driver = { "flex_spi", flex_spi_methods, sizeof(struct flex_spi_softc), }; DRIVER_MODULE(flex_spi, simplebus, flex_spi_driver, 0, 0); SIMPLEBUS_PNP_INFO(flex_spi_compat_data);