Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/flash/flexspi/flex_spi.c
- This file was added.
/*- | |||||
* 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; | |||||
struct bio *bp; | |||||
device_t dev; | |||||
sc = (struct flex_spi_softc *)arg; | |||||
for (;;) { | |||||
dev = sc->dev; | |||||
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 devclass_t flex_spi_devclass; | |||||
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, flex_spi_devclass, 0, 0); | |||||
SIMPLEBUS_PNP_INFO(flex_spi_compat_data); |