mirror of
https://github.com/opnsense/src.git
synced 2026-02-24 10:20:24 -05:00
991 lines
24 KiB
C
991 lines
24 KiB
C
/*-
|
|
* 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;
|
|
|
|
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);
|