mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-22 23:12:32 +00:00
050621aa01
This patch adds a new spi-nand driver which implements the SNFI of mt7622 and mt7629. Unlike the existing snfi driver which makes use of the spi-mem framework and the spi-nand framework with modified ecc support, this driver is implemented directly on the mtd framework with other components untouched, and provides better performance, and behaves exactly the same as the nand framework. Signed-off-by: Weijie Gao <hackpascal@gmail.com>
380 lines
8.2 KiB
C
380 lines
8.2 KiB
C
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
|
|
/*
|
|
* Copyright (C) 2020 MediaTek Inc. All Rights Reserved.
|
|
*
|
|
* Author: Weijie Gao <weijie.gao@mediatek.com>
|
|
*/
|
|
|
|
#include "mtk-snand-def.h"
|
|
|
|
/* ECC registers */
|
|
#define ECC_ENCCON 0x000
|
|
#define ENC_EN BIT(0)
|
|
|
|
#define ECC_ENCCNFG 0x004
|
|
#define ENC_MS_S 16
|
|
#define ENC_BURST_EN BIT(8)
|
|
#define ENC_TNUM_S 0
|
|
|
|
#define ECC_ENCIDLE 0x00c
|
|
#define ENC_IDLE BIT(0)
|
|
|
|
#define ECC_DECCON 0x100
|
|
#define DEC_EN BIT(0)
|
|
|
|
#define ECC_DECCNFG 0x104
|
|
#define DEC_EMPTY_EN BIT(31)
|
|
#define DEC_CS_S 16
|
|
#define DEC_CON_S 12
|
|
#define DEC_CON_CORRECT 3
|
|
#define DEC_BURST_EN BIT(8)
|
|
#define DEC_TNUM_S 0
|
|
|
|
#define ECC_DECIDLE 0x10c
|
|
#define DEC_IDLE BIT(0)
|
|
|
|
#define ECC_DECENUM0 0x114
|
|
#define ECC_DECENUM(n) (ECC_DECENUM0 + (n) * 4)
|
|
|
|
/* ECC_ENCIDLE & ECC_DECIDLE */
|
|
#define ECC_IDLE BIT(0)
|
|
|
|
/* ENC_MODE & DEC_MODE */
|
|
#define ECC_MODE_NFI 1
|
|
|
|
#define ECC_TIMEOUT 500000
|
|
|
|
static const uint8_t mt7622_ecc_caps[] = { 4, 6, 8, 10, 12 };
|
|
|
|
static const uint32_t mt7622_ecc_regs[] = {
|
|
[ECC_DECDONE] = 0x11c,
|
|
};
|
|
|
|
static const struct mtk_ecc_soc_data mtk_ecc_socs[__SNAND_SOC_MAX] = {
|
|
[SNAND_SOC_MT7622] = {
|
|
.ecc_caps = mt7622_ecc_caps,
|
|
.num_ecc_cap = ARRAY_SIZE(mt7622_ecc_caps),
|
|
.regs = mt7622_ecc_regs,
|
|
.mode_shift = 4,
|
|
.errnum_bits = 5,
|
|
.errnum_shift = 5,
|
|
},
|
|
[SNAND_SOC_MT7629] = {
|
|
.ecc_caps = mt7622_ecc_caps,
|
|
.num_ecc_cap = ARRAY_SIZE(mt7622_ecc_caps),
|
|
.regs = mt7622_ecc_regs,
|
|
.mode_shift = 4,
|
|
.errnum_bits = 5,
|
|
.errnum_shift = 5,
|
|
},
|
|
};
|
|
|
|
static inline uint32_t ecc_read32(struct mtk_snand *snf, uint32_t reg)
|
|
{
|
|
return readl(snf->ecc_base + reg);
|
|
}
|
|
|
|
static inline void ecc_write32(struct mtk_snand *snf, uint32_t reg,
|
|
uint32_t val)
|
|
{
|
|
writel(val, snf->ecc_base + reg);
|
|
}
|
|
|
|
static inline void ecc_write16(struct mtk_snand *snf, uint32_t reg,
|
|
uint16_t val)
|
|
{
|
|
writew(val, snf->ecc_base + reg);
|
|
}
|
|
|
|
static int mtk_ecc_poll(struct mtk_snand *snf, uint32_t reg, uint32_t bits)
|
|
{
|
|
uint32_t val;
|
|
|
|
return read16_poll_timeout(snf->ecc_base + reg, val, (val & bits), 0,
|
|
ECC_TIMEOUT);
|
|
}
|
|
|
|
static int mtk_ecc_wait_idle(struct mtk_snand *snf, uint32_t reg)
|
|
{
|
|
int ret;
|
|
|
|
ret = mtk_ecc_poll(snf, reg, ECC_IDLE);
|
|
if (ret) {
|
|
snand_log_ecc(snf->pdev, "ECC engine is busy\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_ecc_setup(struct mtk_snand *snf, void *fmdaddr, uint32_t max_ecc_bytes,
|
|
uint32_t msg_size)
|
|
{
|
|
uint32_t i, val, ecc_msg_bits, ecc_strength;
|
|
int ret;
|
|
|
|
snf->ecc_soc = &mtk_ecc_socs[snf->soc];
|
|
|
|
snf->ecc_parity_bits = fls(1 + 8 * msg_size);
|
|
ecc_strength = max_ecc_bytes * 8 / snf->ecc_parity_bits;
|
|
|
|
for (i = snf->ecc_soc->num_ecc_cap - 1; i >= 0; i--) {
|
|
if (snf->ecc_soc->ecc_caps[i] <= ecc_strength)
|
|
break;
|
|
}
|
|
|
|
if (unlikely(i < 0)) {
|
|
snand_log_ecc(snf->pdev, "Page size %u+%u is not supported\n",
|
|
snf->writesize, snf->oobsize);
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
snf->ecc_strength = snf->ecc_soc->ecc_caps[i];
|
|
snf->ecc_bytes = DIV_ROUND_UP(snf->ecc_strength * snf->ecc_parity_bits,
|
|
8);
|
|
|
|
/* Encoder config */
|
|
ecc_write16(snf, ECC_ENCCON, 0);
|
|
ret = mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ecc_msg_bits = msg_size * 8;
|
|
val = (ecc_msg_bits << ENC_MS_S) |
|
|
(ECC_MODE_NFI << snf->ecc_soc->mode_shift) | i;
|
|
ecc_write32(snf, ECC_ENCCNFG, val);
|
|
|
|
/* Decoder config */
|
|
ecc_write16(snf, ECC_DECCON, 0);
|
|
ret = mtk_ecc_wait_idle(snf, ECC_DECIDLE);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ecc_msg_bits += snf->ecc_strength * snf->ecc_parity_bits;
|
|
val = DEC_EMPTY_EN | (ecc_msg_bits << DEC_CS_S) |
|
|
(DEC_CON_CORRECT << DEC_CON_S) |
|
|
(ECC_MODE_NFI << snf->ecc_soc->mode_shift) | i;
|
|
ecc_write32(snf, ECC_DECCNFG, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mtk_snand_ecc_encoder_start(struct mtk_snand *snf)
|
|
{
|
|
int ret;
|
|
|
|
ret = mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
|
|
if (ret) {
|
|
ecc_write16(snf, ECC_ENCCON, 0);
|
|
mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
|
|
}
|
|
|
|
ecc_write16(snf, ECC_ENCCON, ENC_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mtk_snand_ecc_encoder_stop(struct mtk_snand *snf)
|
|
{
|
|
mtk_ecc_wait_idle(snf, ECC_ENCIDLE);
|
|
ecc_write16(snf, ECC_ENCCON, 0);
|
|
}
|
|
|
|
int mtk_snand_ecc_decoder_start(struct mtk_snand *snf)
|
|
{
|
|
int ret;
|
|
|
|
ret = mtk_ecc_wait_idle(snf, ECC_DECIDLE);
|
|
if (ret) {
|
|
ecc_write16(snf, ECC_DECCON, 0);
|
|
mtk_ecc_wait_idle(snf, ECC_DECIDLE);
|
|
}
|
|
|
|
ecc_write16(snf, ECC_DECCON, DEC_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mtk_snand_ecc_decoder_stop(struct mtk_snand *snf)
|
|
{
|
|
mtk_ecc_wait_idle(snf, ECC_DECIDLE);
|
|
ecc_write16(snf, ECC_DECCON, 0);
|
|
}
|
|
|
|
int mtk_ecc_wait_decoder_done(struct mtk_snand *snf)
|
|
{
|
|
uint16_t val, step_mask = (1 << snf->ecc_steps) - 1;
|
|
uint32_t reg = snf->ecc_soc->regs[ECC_DECDONE];
|
|
int ret;
|
|
|
|
ret = read16_poll_timeout(snf->ecc_base + reg, val,
|
|
(val & step_mask) == step_mask, 0,
|
|
ECC_TIMEOUT);
|
|
if (ret)
|
|
snand_log_ecc(snf->pdev, "ECC decoder is busy\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
int mtk_ecc_check_decode_error(struct mtk_snand *snf)
|
|
{
|
|
uint32_t i, regi, fi, errnum;
|
|
uint32_t errnum_shift = snf->ecc_soc->errnum_shift;
|
|
uint32_t errnum_mask = (1 << snf->ecc_soc->errnum_bits) - 1;
|
|
int ret = 0;
|
|
|
|
for (i = 0; i < snf->ecc_steps; i++) {
|
|
regi = i / 4;
|
|
fi = i % 4;
|
|
|
|
errnum = ecc_read32(snf, ECC_DECENUM(regi));
|
|
errnum = (errnum >> (fi * errnum_shift)) & errnum_mask;
|
|
|
|
if (errnum <= snf->ecc_strength) {
|
|
snf->sect_bf[i] = errnum;
|
|
} else {
|
|
snf->sect_bf[i] = -1;
|
|
ret = -EBADMSG;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mtk_ecc_check_buf_bitflips(struct mtk_snand *snf, const void *buf,
|
|
size_t len, uint32_t bitflips)
|
|
{
|
|
const uint8_t *buf8 = buf;
|
|
const uint32_t *buf32;
|
|
uint32_t d, weight;
|
|
|
|
while (len && ((uintptr_t)buf8) % sizeof(uint32_t)) {
|
|
weight = hweight8(*buf8);
|
|
bitflips += BITS_PER_BYTE - weight;
|
|
buf8++;
|
|
len--;
|
|
|
|
if (bitflips > snf->ecc_strength)
|
|
return -EBADMSG;
|
|
}
|
|
|
|
buf32 = (const uint32_t *)buf8;
|
|
while (len >= sizeof(uint32_t)) {
|
|
d = *buf32;
|
|
|
|
if (d != ~0) {
|
|
weight = hweight32(d);
|
|
bitflips += sizeof(uint32_t) * BITS_PER_BYTE - weight;
|
|
}
|
|
|
|
buf32++;
|
|
len -= sizeof(uint32_t);
|
|
|
|
if (bitflips > snf->ecc_strength)
|
|
return -EBADMSG;
|
|
}
|
|
|
|
buf8 = (const uint8_t *)buf32;
|
|
while (len) {
|
|
weight = hweight8(*buf8);
|
|
bitflips += BITS_PER_BYTE - weight;
|
|
buf8++;
|
|
len--;
|
|
|
|
if (bitflips > snf->ecc_strength)
|
|
return -EBADMSG;
|
|
}
|
|
|
|
return bitflips;
|
|
}
|
|
|
|
static int mtk_ecc_check_parity_bitflips(struct mtk_snand *snf, const void *buf,
|
|
uint32_t bits, uint32_t bitflips)
|
|
{
|
|
uint32_t len, i;
|
|
uint8_t b;
|
|
int rc;
|
|
|
|
len = bits >> 3;
|
|
bits &= 7;
|
|
|
|
rc = mtk_ecc_check_buf_bitflips(snf, buf, len, bitflips);
|
|
if (!bits || rc < 0)
|
|
return rc;
|
|
|
|
bitflips = rc;
|
|
|
|
/* We want a precise count of bits */
|
|
b = ((const uint8_t *)buf)[len];
|
|
for (i = 0; i < bits; i++) {
|
|
if (!(b & BIT(i)))
|
|
bitflips++;
|
|
}
|
|
|
|
if (bitflips > snf->ecc_strength)
|
|
return -EBADMSG;
|
|
|
|
return bitflips;
|
|
}
|
|
|
|
static void mtk_ecc_reset_parity(void *buf, uint32_t bits)
|
|
{
|
|
uint32_t len;
|
|
|
|
len = bits >> 3;
|
|
bits &= 7;
|
|
|
|
memset(buf, 0xff, len);
|
|
|
|
/* Only reset bits protected by ECC to 1 */
|
|
if (bits)
|
|
((uint8_t *)buf)[len] |= GENMASK(bits - 1, 0);
|
|
}
|
|
|
|
int mtk_ecc_fixup_empty_sector(struct mtk_snand *snf, uint32_t sect)
|
|
{
|
|
uint32_t ecc_bytes = snf->spare_per_sector - snf->nfi_soc->fdm_size;
|
|
uint8_t *oob = snf->page_cache + snf->writesize;
|
|
uint8_t *data_ptr, *fdm_ptr, *ecc_ptr;
|
|
int bitflips = 0, ecc_bits, parity_bits;
|
|
|
|
parity_bits = fls(snf->nfi_soc->sector_size * 8);
|
|
ecc_bits = snf->ecc_strength * parity_bits;
|
|
|
|
data_ptr = snf->page_cache + sect * snf->nfi_soc->sector_size;
|
|
fdm_ptr = oob + sect * snf->nfi_soc->fdm_size;
|
|
ecc_ptr = oob + snf->ecc_steps * snf->nfi_soc->fdm_size +
|
|
sect * ecc_bytes;
|
|
|
|
/*
|
|
* Check whether DATA + FDM + ECC of a sector contains correctable
|
|
* bitflips
|
|
*/
|
|
bitflips = mtk_ecc_check_buf_bitflips(snf, data_ptr,
|
|
snf->nfi_soc->sector_size,
|
|
bitflips);
|
|
if (bitflips < 0)
|
|
return -EBADMSG;
|
|
|
|
bitflips = mtk_ecc_check_buf_bitflips(snf, fdm_ptr,
|
|
snf->nfi_soc->fdm_ecc_size,
|
|
bitflips);
|
|
if (bitflips < 0)
|
|
return -EBADMSG;
|
|
|
|
bitflips = mtk_ecc_check_parity_bitflips(snf, ecc_ptr, ecc_bits,
|
|
bitflips);
|
|
if (bitflips < 0)
|
|
return -EBADMSG;
|
|
|
|
if (!bitflips)
|
|
return 0;
|
|
|
|
/* Reset the data of this sector to 0xff */
|
|
memset(data_ptr, 0xff, snf->nfi_soc->sector_size);
|
|
memset(fdm_ptr, 0xff, snf->nfi_soc->fdm_ecc_size);
|
|
mtk_ecc_reset_parity(ecc_ptr, ecc_bits);
|
|
|
|
return bitflips;
|
|
}
|