commit
ad8c9f6146
@ -1,4 +1,4 @@ |
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
spinand-objs := core.o macronix.o micron.o winbond.o
|
||||
spinand-objs := core.o gigadevice.o macronix.o micron.o winbond.o
|
||||
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
||||
|
@ -0,0 +1,135 @@ |
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2018 Stefan Roese <sr@denx.de> |
||||
* |
||||
* Derived from drivers/mtd/nand/spi/micron.c |
||||
* Copyright (c) 2016-2017 Micron Technology, Inc. |
||||
*/ |
||||
|
||||
#ifndef __UBOOT__ |
||||
#include <linux/device.h> |
||||
#include <linux/kernel.h> |
||||
#endif |
||||
#include <linux/mtd/spinand.h> |
||||
|
||||
#define SPINAND_MFR_GIGADEVICE 0xc8 |
||||
|
||||
#define GIGADEVICE_STATUS_ECC_MASK GENMASK(5, 4) |
||||
#define GIGADEVICE_STATUS_ECC_NO_BITFLIPS (0 << 4) |
||||
#define GIGADEVICE_STATUS_ECC_1TO7_BITFLIPS (1 << 4) |
||||
#define GIGADEVICE_STATUS_ECC_8_BITFLIPS (3 << 4) |
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants, |
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0), |
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0), |
||||
SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0), |
||||
SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0), |
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0), |
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0)); |
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants, |
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0), |
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0)); |
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants, |
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0), |
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0)); |
||||
|
||||
static int gd5f1gq4u_ooblayout_ecc(struct mtd_info *mtd, int section, |
||||
struct mtd_oob_region *region) |
||||
{ |
||||
if (section) |
||||
return -ERANGE; |
||||
|
||||
region->offset = 64; |
||||
region->length = 64; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int gd5f1gq4u_ooblayout_free(struct mtd_info *mtd, int section, |
||||
struct mtd_oob_region *region) |
||||
{ |
||||
if (section) |
||||
return -ERANGE; |
||||
|
||||
/* Reserve 2 bytes for the BBM. */ |
||||
region->offset = 2; |
||||
region->length = 62; |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct mtd_ooblayout_ops gd5f1gq4u_ooblayout = { |
||||
.ecc = gd5f1gq4u_ooblayout_ecc, |
||||
.free = gd5f1gq4u_ooblayout_free, |
||||
}; |
||||
|
||||
static int gd5f1gq4u_ecc_get_status(struct spinand_device *spinand, |
||||
u8 status) |
||||
{ |
||||
if (status) |
||||
debug("%s (%d): status=%02x\n", __func__, __LINE__, status); |
||||
|
||||
switch (status & GIGADEVICE_STATUS_ECC_MASK) { |
||||
case STATUS_ECC_NO_BITFLIPS: |
||||
return 0; |
||||
|
||||
case GIGADEVICE_STATUS_ECC_1TO7_BITFLIPS: |
||||
return 7; |
||||
|
||||
case GIGADEVICE_STATUS_ECC_8_BITFLIPS: |
||||
return 8; |
||||
|
||||
case STATUS_ECC_UNCOR_ERROR: |
||||
return -EBADMSG; |
||||
|
||||
default: |
||||
break; |
||||
} |
||||
|
||||
return -EINVAL; |
||||
} |
||||
|
||||
static const struct spinand_info gigadevice_spinand_table[] = { |
||||
SPINAND_INFO("GD5F1GQ4UC", 0xd1, |
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 1, 1, 1), |
||||
NAND_ECCREQ(8, 2048), |
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants, |
||||
&write_cache_variants, |
||||
&update_cache_variants), |
||||
0, |
||||
SPINAND_ECCINFO(&gd5f1gq4u_ooblayout, |
||||
gd5f1gq4u_ecc_get_status)), |
||||
}; |
||||
|
||||
static int gigadevice_spinand_detect(struct spinand_device *spinand) |
||||
{ |
||||
u8 *id = spinand->id.data; |
||||
int ret; |
||||
|
||||
/*
|
||||
* Gigadevice SPI NAND read ID need a dummy byte, |
||||
* so the first byte in raw_id is dummy. |
||||
*/ |
||||
if (id[1] != SPINAND_MFR_GIGADEVICE) |
||||
return 0; |
||||
|
||||
ret = spinand_match_and_init(spinand, gigadevice_spinand_table, |
||||
ARRAY_SIZE(gigadevice_spinand_table), |
||||
id[2]); |
||||
if (ret) |
||||
return ret; |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = { |
||||
.detect = gigadevice_spinand_detect, |
||||
}; |
||||
|
||||
const struct spinand_manufacturer gigadevice_spinand_manufacturer = { |
||||
.id = SPINAND_MFR_GIGADEVICE, |
||||
.name = "GigaDevice", |
||||
.ops = &gigadevice_spinand_manuf_ops, |
||||
}; |
@ -0,0 +1,312 @@ |
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2018 Stefan Roese <sr@denx.de> |
||||
* |
||||
* Derived from the Linux driver version drivers/spi/spi-mt7621.c |
||||
* Copyright (C) 2011 Sergiy <piratfm@gmail.com> |
||||
* Copyright (C) 2011-2013 Gabor Juhos <juhosg@openwrt.org> |
||||
* Copyright (C) 2014-2015 Felix Fietkau <nbd@nbd.name> |
||||
*/ |
||||
|
||||
#include <common.h> |
||||
#include <dm.h> |
||||
#include <spi.h> |
||||
#include <wait_bit.h> |
||||
#include <linux/io.h> |
||||
|
||||
#define SPI_MSG_SIZE_MAX 32 /* SPI message chunk size */ |
||||
/* Enough for SPI NAND page read / write with page size 2048 bytes */ |
||||
#define SPI_MSG_SIZE_OVERALL (2048 + 16) |
||||
|
||||
#define MT7621_SPI_TRANS 0x00 |
||||
#define MT7621_SPI_TRANS_START BIT(8) |
||||
#define MT7621_SPI_TRANS_BUSY BIT(16) |
||||
|
||||
#define MT7621_SPI_OPCODE 0x04 |
||||
#define MT7621_SPI_DATA0 0x08 |
||||
#define MT7621_SPI_DATA4 0x18 |
||||
#define MT7621_SPI_MASTER 0x28 |
||||
#define MT7621_SPI_MOREBUF 0x2c |
||||
#define MT7621_SPI_POLAR 0x38 |
||||
|
||||
#define MT7621_LSB_FIRST BIT(3) |
||||
#define MT7621_CPOL BIT(4) |
||||
#define MT7621_CPHA BIT(5) |
||||
|
||||
#define MASTER_MORE_BUFMODE BIT(2) |
||||
#define MASTER_RS_CLK_SEL GENMASK(27, 16) |
||||
#define MASTER_RS_CLK_SEL_SHIFT 16 |
||||
#define MASTER_RS_SLAVE_SEL GENMASK(31, 29) |
||||
|
||||
struct mt7621_spi { |
||||
void __iomem *base; |
||||
unsigned int sys_freq; |
||||
u32 data[(SPI_MSG_SIZE_OVERALL / 4) + 1]; |
||||
int tx_len; |
||||
}; |
||||
|
||||
static void mt7621_spi_reset(struct mt7621_spi *rs, int duplex) |
||||
{ |
||||
setbits_le32(rs->base + MT7621_SPI_MASTER, |
||||
MASTER_RS_SLAVE_SEL | MASTER_MORE_BUFMODE); |
||||
} |
||||
|
||||
static void mt7621_spi_set_cs(struct mt7621_spi *rs, int cs, int enable) |
||||
{ |
||||
u32 val = 0; |
||||
|
||||
debug("%s: cs#%d -> %s\n", __func__, cs, enable ? "enable" : "disable"); |
||||
if (enable) |
||||
val = BIT(cs); |
||||
iowrite32(val, rs->base + MT7621_SPI_POLAR); |
||||
} |
||||
|
||||
static int mt7621_spi_set_mode(struct udevice *bus, uint mode) |
||||
{ |
||||
struct mt7621_spi *rs = dev_get_priv(bus); |
||||
u32 reg; |
||||
|
||||
debug("%s: mode=0x%08x\n", __func__, mode); |
||||
reg = ioread32(rs->base + MT7621_SPI_MASTER); |
||||
|
||||
reg &= ~MT7621_LSB_FIRST; |
||||
if (mode & SPI_LSB_FIRST) |
||||
reg |= MT7621_LSB_FIRST; |
||||
|
||||
reg &= ~(MT7621_CPHA | MT7621_CPOL); |
||||
switch (mode & (SPI_CPOL | SPI_CPHA)) { |
||||
case SPI_MODE_0: |
||||
break; |
||||
case SPI_MODE_1: |
||||
reg |= MT7621_CPHA; |
||||
break; |
||||
case SPI_MODE_2: |
||||
reg |= MT7621_CPOL; |
||||
break; |
||||
case SPI_MODE_3: |
||||
reg |= MT7621_CPOL | MT7621_CPHA; |
||||
break; |
||||
} |
||||
iowrite32(reg, rs->base + MT7621_SPI_MASTER); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int mt7621_spi_set_speed(struct udevice *bus, uint speed) |
||||
{ |
||||
struct mt7621_spi *rs = dev_get_priv(bus); |
||||
u32 rate; |
||||
u32 reg; |
||||
|
||||
debug("%s: speed=%d\n", __func__, speed); |
||||
rate = DIV_ROUND_UP(rs->sys_freq, speed); |
||||
debug("rate:%u\n", rate); |
||||
|
||||
if (rate > 4097) |
||||
return -EINVAL; |
||||
|
||||
if (rate < 2) |
||||
rate = 2; |
||||
|
||||
reg = ioread32(rs->base + MT7621_SPI_MASTER); |
||||
reg &= ~MASTER_RS_CLK_SEL; |
||||
reg |= (rate - 2) << MASTER_RS_CLK_SEL_SHIFT; |
||||
iowrite32(reg, rs->base + MT7621_SPI_MASTER); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static inline int mt7621_spi_wait_till_ready(struct mt7621_spi *rs) |
||||
{ |
||||
int ret; |
||||
|
||||
ret = wait_for_bit_le32(rs->base + MT7621_SPI_TRANS, |
||||
MT7621_SPI_TRANS_BUSY, 0, 10, 0); |
||||
if (ret) |
||||
pr_err("Timeout in %s!\n", __func__); |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
static int mt7621_spi_xfer(struct udevice *dev, unsigned int bitlen, |
||||
const void *dout, void *din, unsigned long flags) |
||||
{ |
||||
struct udevice *bus = dev->parent; |
||||
struct mt7621_spi *rs = dev_get_priv(bus); |
||||
const u8 *tx_buf = dout; |
||||
u8 *ptr = (u8 *)dout; |
||||
u8 *rx_buf = din; |
||||
int total_size = bitlen >> 3; |
||||
int chunk_size; |
||||
int rx_len = 0; |
||||
u32 data[(SPI_MSG_SIZE_MAX / 4) + 1] = { 0 }; |
||||
u32 val; |
||||
int i; |
||||
|
||||
debug("%s: dout=%p, din=%p, len=%x, flags=%lx\n", __func__, dout, din, |
||||
total_size, flags); |
||||
|
||||
/*
|
||||
* This driver only supports half-duplex, so complain and bail out |
||||
* upon full-duplex messages |
||||
*/ |
||||
if (dout && din) { |
||||
printf("Only half-duplex SPI transfer supported\n"); |
||||
return -EIO; |
||||
} |
||||
|
||||
if (dout) { |
||||
debug("TX-DATA: "); |
||||
for (i = 0; i < total_size; i++) |
||||
debug("%02x ", *ptr++); |
||||
debug("\n"); |
||||
} |
||||
|
||||
mt7621_spi_wait_till_ready(rs); |
||||
|
||||
/*
|
||||
* Set CS active upon start of SPI message. This message can |
||||
* be split upon multiple calls to this xfer function |
||||
*/ |
||||
if (flags & SPI_XFER_BEGIN) |
||||
mt7621_spi_set_cs(rs, spi_chip_select(dev), 1); |
||||
|
||||
while (total_size > 0) { |
||||
/* Don't exceed the max xfer size */ |
||||
chunk_size = min_t(int, total_size, SPI_MSG_SIZE_MAX); |
||||
|
||||
/*
|
||||
* We might have some TX data buffered from the last xfer |
||||
* message. Make sure, that this does not exceed the max |
||||
* xfer size |
||||
*/ |
||||
if (rs->tx_len > 4) |
||||
chunk_size -= rs->tx_len; |
||||
if (din) |
||||
rx_len = chunk_size; |
||||
|
||||
if (tx_buf) { |
||||
/* Check if this message does not exceed the buffer */ |
||||
if ((chunk_size + rs->tx_len) > SPI_MSG_SIZE_OVERALL) { |
||||
printf("TX message size too big (%d)\n", |
||||
chunk_size + rs->tx_len); |
||||
return -EMSGSIZE; |
||||
} |
||||
|
||||
/*
|
||||
* Write all TX data into internal buffer to collect |
||||
* all TX messages into one buffer (might be split into |
||||
* multiple calls to this function) |
||||
*/ |
||||
for (i = 0; i < chunk_size; i++, rs->tx_len++) { |
||||
rs->data[rs->tx_len / 4] |= |
||||
tx_buf[i] << (8 * (rs->tx_len & 3)); |
||||
} |
||||
} |
||||
|
||||
if (flags & SPI_XFER_END) { |
||||
/* Write TX data into controller */ |
||||
if (rs->tx_len) { |
||||
rs->data[0] = swab32(rs->data[0]); |
||||
if (rs->tx_len < 4) |
||||
rs->data[0] >>= (4 - rs->tx_len) * 8; |
||||
|
||||
for (i = 0; i < rs->tx_len; i += 4) { |
||||
iowrite32(rs->data[i / 4], rs->base + |
||||
MT7621_SPI_OPCODE + i); |
||||
} |
||||
} |
||||
|
||||
/* Write length into controller */ |
||||
val = (min_t(int, rs->tx_len, 4) * 8) << 24; |
||||
if (rs->tx_len > 4) |
||||
val |= (rs->tx_len - 4) * 8; |
||||
val |= (rx_len * 8) << 12; |
||||
iowrite32(val, rs->base + MT7621_SPI_MOREBUF); |
||||
|
||||
/* Start the xfer */ |
||||
setbits_le32(rs->base + MT7621_SPI_TRANS, |
||||
MT7621_SPI_TRANS_START); |
||||
|
||||
/* Wait until xfer is finished on bus */ |
||||
mt7621_spi_wait_till_ready(rs); |
||||
|
||||
/* Reset TX length and TX buffer for next xfer */ |
||||
rs->tx_len = 0; |
||||
memset(rs->data, 0, sizeof(rs->data)); |
||||
} |
||||
|
||||
for (i = 0; i < rx_len; i += 4) |
||||
data[i / 4] = ioread32(rs->base + MT7621_SPI_DATA0 + i); |
||||
|
||||
if (rx_len) { |
||||
debug("RX-DATA: "); |
||||
for (i = 0; i < rx_len; i++) { |
||||
rx_buf[i] = data[i / 4] >> (8 * (i & 3)); |
||||
debug("%02x ", rx_buf[i]); |
||||
} |
||||
debug("\n"); |
||||
} |
||||
|
||||
if (tx_buf) |
||||
tx_buf += chunk_size; |
||||
if (rx_buf) |
||||
rx_buf += chunk_size; |
||||
total_size -= chunk_size; |
||||
} |
||||
|
||||
/* Wait until xfer is finished on bus and de-assert CS */ |
||||
mt7621_spi_wait_till_ready(rs); |
||||
if (flags & SPI_XFER_END) |
||||
mt7621_spi_set_cs(rs, spi_chip_select(dev), 0); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static int mt7621_spi_probe(struct udevice *dev) |
||||
{ |
||||
struct mt7621_spi *rs = dev_get_priv(dev); |
||||
|
||||
rs->base = dev_remap_addr(dev); |
||||
if (!rs->base) |
||||
return -EINVAL; |
||||
|
||||
/*
|
||||
* Read input clock via DT for now. At some point this should be |
||||
* replaced by implementing a clock driver for this SoC and getting |
||||
* the SPI frequency via this clock driver. |
||||
*/ |
||||
rs->sys_freq = dev_read_u32_default(dev, "clock-frequency", 0); |
||||
if (!rs->sys_freq) { |
||||
printf("Please provide clock-frequency!\n"); |
||||
return -EINVAL; |
||||
} |
||||
|
||||
mt7621_spi_reset(rs, 0); |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
static const struct dm_spi_ops mt7621_spi_ops = { |
||||
.set_mode = mt7621_spi_set_mode, |
||||
.set_speed = mt7621_spi_set_speed, |
||||
.xfer = mt7621_spi_xfer, |
||||
/*
|
||||
* cs_info is not needed, since we require all chip selects to be |
||||
* in the device tree explicitly |
||||
*/ |
||||
}; |
||||
|
||||
static const struct udevice_id mt7621_spi_ids[] = { |
||||
{ .compatible = "ralink,mt7621-spi" }, |
||||
{ } |
||||
}; |
||||
|
||||
U_BOOT_DRIVER(mt7621_spi) = { |
||||
.name = "mt7621_spi", |
||||
.id = UCLASS_SPI, |
||||
.of_match = mt7621_spi_ids, |
||||
.ops = &mt7621_spi_ops, |
||||
.priv_auto_alloc_size = sizeof(struct mt7621_spi), |
||||
.probe = mt7621_spi_probe, |
||||
}; |
Loading…
Reference in new issue