forked from Openwrt/openwrt
This is needed to restore compatibility with legacy brcmnand controllers. 3bfb22cecfe6 mtd: rawnand: brcmnand: legacy exec_op implementation 528b541b71cf mtd: nand: brcmnand: fix NAND timeout when accessing eMMC 56fce7547004 mtd: rawnand: brcmnand: remove unused parameters Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
300 lines
8.6 KiB
Diff
300 lines
8.6 KiB
Diff
From 3bfb22cecfe6b6f0d8ee56ef4b533cf68599c5d9 Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?=C3=81lvaro=20Fern=C3=A1ndez=20Rojas?= <noltari@gmail.com>
|
|
Date: Wed, 21 May 2025 10:03:25 +0200
|
|
Subject: [PATCH] mtd: rawnand: brcmnand: legacy exec_op implementation
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Commit 3c8260ce7663 ("mtd: rawnand: brcmnand: exec_op implementation")
|
|
removed legacy interface functions, breaking < v5.0 controllers support.
|
|
In order to fix older controllers we need to add an alternative exec_op
|
|
implementation which doesn't rely on low level registers.
|
|
|
|
Fixes: 3c8260ce7663 ("mtd: rawnand: brcmnand: exec_op implementation")
|
|
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
|
|
Reviewed-by: David Regan <dregan@broadcom.com>
|
|
Reviewed-by: Florian Fainelli <florian.fainelli@broadcom.com>
|
|
Reviewed-by: William Zhang <william.zhang@broadcom.com>
|
|
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|
|
---
|
|
drivers/mtd/nand/raw/brcmnand/brcmnand.c | 222 ++++++++++++++++++++++-
|
|
1 file changed, 215 insertions(+), 7 deletions(-)
|
|
|
|
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
|
|
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
|
|
@@ -65,6 +65,7 @@ module_param(wp_on, int, 0444);
|
|
#define CMD_PARAMETER_READ 0x0e
|
|
#define CMD_PARAMETER_CHANGE_COL 0x0f
|
|
#define CMD_LOW_LEVEL_OP 0x10
|
|
+#define CMD_NOT_SUPPORTED 0xff
|
|
|
|
struct brcm_nand_dma_desc {
|
|
u32 next_desc;
|
|
@@ -199,6 +200,30 @@ static const u16 flash_dma_regs_v4[] = {
|
|
[FLASH_DMA_CURRENT_DESC_EXT] = 0x34,
|
|
};
|
|
|
|
+/* Native command conversion for legacy controllers (< v5.0) */
|
|
+static const u8 native_cmd_conv[] = {
|
|
+ [NAND_CMD_READ0] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_READ1] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_RNDOUT] = CMD_PARAMETER_CHANGE_COL,
|
|
+ [NAND_CMD_PAGEPROG] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_READOOB] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_ERASE1] = CMD_BLOCK_ERASE,
|
|
+ [NAND_CMD_STATUS] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_SEQIN] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_RNDIN] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_READID] = CMD_DEVICE_ID_READ,
|
|
+ [NAND_CMD_ERASE2] = CMD_NULL,
|
|
+ [NAND_CMD_PARAM] = CMD_PARAMETER_READ,
|
|
+ [NAND_CMD_GET_FEATURES] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_SET_FEATURES] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_RESET] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_READSTART] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_READCACHESEQ] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_READCACHEEND] = CMD_NOT_SUPPORTED,
|
|
+ [NAND_CMD_RNDOUTSTART] = CMD_NULL,
|
|
+ [NAND_CMD_CACHEDPROG] = CMD_NOT_SUPPORTED,
|
|
+};
|
|
+
|
|
/* Controller feature flags */
|
|
enum {
|
|
BRCMNAND_HAS_1K_SECTORS = BIT(0),
|
|
@@ -237,6 +262,12 @@ struct brcmnand_controller {
|
|
/* List of NAND hosts (one for each chip-select) */
|
|
struct list_head host_list;
|
|
|
|
+ /* Functions to be called from exec_op */
|
|
+ int (*check_instr)(struct nand_chip *chip,
|
|
+ const struct nand_operation *op);
|
|
+ int (*exec_instr)(struct nand_chip *chip,
|
|
+ const struct nand_operation *op);
|
|
+
|
|
/* EDU info, per-transaction */
|
|
const u16 *edu_offsets;
|
|
void __iomem *edu_base;
|
|
@@ -2478,18 +2509,190 @@ static int brcmnand_op_is_reset(const st
|
|
return 0;
|
|
}
|
|
|
|
+static int brcmnand_check_instructions(struct nand_chip *chip,
|
|
+ const struct nand_operation *op)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int brcmnand_exec_instructions(struct nand_chip *chip,
|
|
+ const struct nand_operation *op)
|
|
+{
|
|
+ struct brcmnand_host *host = nand_get_controller_data(chip);
|
|
+ unsigned int i;
|
|
+ int ret = 0;
|
|
+
|
|
+ for (i = 0; i < op->ninstrs; i++) {
|
|
+ ret = brcmnand_exec_instr(host, i, op);
|
|
+ if (ret)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int brcmnand_check_instructions_legacy(struct nand_chip *chip,
|
|
+ const struct nand_operation *op)
|
|
+{
|
|
+ const struct nand_op_instr *instr;
|
|
+ unsigned int i;
|
|
+ u8 cmd;
|
|
+
|
|
+ for (i = 0; i < op->ninstrs; i++) {
|
|
+ instr = &op->instrs[i];
|
|
+
|
|
+ switch (instr->type) {
|
|
+ case NAND_OP_CMD_INSTR:
|
|
+ cmd = native_cmd_conv[instr->ctx.cmd.opcode];
|
|
+ if (cmd == CMD_NOT_SUPPORTED)
|
|
+ return -EOPNOTSUPP;
|
|
+ break;
|
|
+ case NAND_OP_ADDR_INSTR:
|
|
+ case NAND_OP_DATA_IN_INSTR:
|
|
+ case NAND_OP_WAITRDY_INSTR:
|
|
+ break;
|
|
+ default:
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int brcmnand_exec_instructions_legacy(struct nand_chip *chip,
|
|
+ const struct nand_operation *op)
|
|
+{
|
|
+ struct mtd_info *mtd = nand_to_mtd(chip);
|
|
+ struct brcmnand_host *host = nand_get_controller_data(chip);
|
|
+ struct brcmnand_controller *ctrl = host->ctrl;
|
|
+ const struct nand_op_instr *instr;
|
|
+ unsigned int i, j;
|
|
+ u8 cmd = CMD_NULL, last_cmd = CMD_NULL;
|
|
+ int ret = 0;
|
|
+ u64 last_addr;
|
|
+
|
|
+ for (i = 0; i < op->ninstrs; i++) {
|
|
+ instr = &op->instrs[i];
|
|
+
|
|
+ if (instr->type == NAND_OP_CMD_INSTR) {
|
|
+ cmd = native_cmd_conv[instr->ctx.cmd.opcode];
|
|
+ if (cmd == CMD_NOT_SUPPORTED) {
|
|
+ dev_err(ctrl->dev, "unsupported cmd=%d\n",
|
|
+ instr->ctx.cmd.opcode);
|
|
+ ret = -EOPNOTSUPP;
|
|
+ break;
|
|
+ }
|
|
+ } else if (instr->type == NAND_OP_ADDR_INSTR) {
|
|
+ u64 addr = 0;
|
|
+
|
|
+ if (cmd == CMD_NULL)
|
|
+ continue;
|
|
+
|
|
+ if (instr->ctx.addr.naddrs > 8) {
|
|
+ dev_err(ctrl->dev, "unsupported naddrs=%u\n",
|
|
+ instr->ctx.addr.naddrs);
|
|
+ ret = -EOPNOTSUPP;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ for (j = 0; j < instr->ctx.addr.naddrs; j++)
|
|
+ addr |= (instr->ctx.addr.addrs[j]) << (j << 3);
|
|
+
|
|
+ if (cmd == CMD_BLOCK_ERASE)
|
|
+ addr <<= chip->page_shift;
|
|
+ else if (cmd == CMD_PARAMETER_CHANGE_COL)
|
|
+ addr &= ~((u64)(FC_BYTES - 1));
|
|
+
|
|
+ brcmnand_set_cmd_addr(mtd, addr);
|
|
+ brcmnand_send_cmd(host, cmd);
|
|
+ last_addr = addr;
|
|
+ last_cmd = cmd;
|
|
+ cmd = CMD_NULL;
|
|
+ brcmnand_waitfunc(chip);
|
|
+
|
|
+ if (last_cmd == CMD_PARAMETER_READ ||
|
|
+ last_cmd == CMD_PARAMETER_CHANGE_COL) {
|
|
+ /* Copy flash cache word-wise */
|
|
+ u32 *flash_cache = (u32 *)ctrl->flash_cache;
|
|
+
|
|
+ brcmnand_soc_data_bus_prepare(ctrl->soc, true);
|
|
+
|
|
+ /*
|
|
+ * Must cache the FLASH_CACHE now, since changes in
|
|
+ * SECTOR_SIZE_1K may invalidate it
|
|
+ */
|
|
+ for (j = 0; j < FC_WORDS; j++)
|
|
+ /*
|
|
+ * Flash cache is big endian for parameter pages, at
|
|
+ * least on STB SoCs
|
|
+ */
|
|
+ flash_cache[j] = be32_to_cpu(brcmnand_read_fc(ctrl, j));
|
|
+
|
|
+ brcmnand_soc_data_bus_unprepare(ctrl->soc, true);
|
|
+ }
|
|
+ } else if (instr->type == NAND_OP_DATA_IN_INSTR) {
|
|
+ u8 *in = instr->ctx.data.buf.in;
|
|
+
|
|
+ if (last_cmd == CMD_DEVICE_ID_READ) {
|
|
+ u32 val;
|
|
+
|
|
+ if (instr->ctx.data.len > 8) {
|
|
+ dev_err(ctrl->dev, "unsupported len=%u\n",
|
|
+ instr->ctx.data.len);
|
|
+ ret = -EOPNOTSUPP;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ for (j = 0; j < instr->ctx.data.len; j++) {
|
|
+ if (j == 0)
|
|
+ val = brcmnand_read_reg(ctrl, BRCMNAND_ID);
|
|
+ else if (j == 4)
|
|
+ val = brcmnand_read_reg(ctrl, BRCMNAND_ID_EXT);
|
|
+
|
|
+ in[j] = (val >> (24 - ((j % 4) << 3))) & 0xff;
|
|
+ }
|
|
+ } else if (last_cmd == CMD_PARAMETER_READ ||
|
|
+ last_cmd == CMD_PARAMETER_CHANGE_COL) {
|
|
+ u64 addr;
|
|
+ u32 offs;
|
|
+
|
|
+ for (j = 0; j < instr->ctx.data.len; j++) {
|
|
+ addr = last_addr + j;
|
|
+ offs = addr & (FC_BYTES - 1);
|
|
+
|
|
+ if (j > 0 && offs == 0)
|
|
+ nand_change_read_column_op(chip, addr, NULL, 0,
|
|
+ false);
|
|
+
|
|
+ in[j] = ctrl->flash_cache[offs];
|
|
+ }
|
|
+ }
|
|
+ } else if (instr->type == NAND_OP_WAITRDY_INSTR) {
|
|
+ ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
|
|
+ if (ret)
|
|
+ break;
|
|
+ } else {
|
|
+ dev_err(ctrl->dev, "unsupported instruction type: %d\n", instr->type);
|
|
+ ret = -EOPNOTSUPP;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
static int brcmnand_exec_op(struct nand_chip *chip,
|
|
const struct nand_operation *op,
|
|
bool check_only)
|
|
{
|
|
struct brcmnand_host *host = nand_get_controller_data(chip);
|
|
+ struct brcmnand_controller *ctrl = host->ctrl;
|
|
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
u8 *status;
|
|
- unsigned int i;
|
|
int ret = 0;
|
|
|
|
if (check_only)
|
|
- return 0;
|
|
+ return ctrl->check_instr(chip, op);
|
|
|
|
if (brcmnand_op_is_status(op)) {
|
|
status = op->instrs[1].ctx.data.buf.in;
|
|
@@ -2513,11 +2716,7 @@ static int brcmnand_exec_op(struct nand_
|
|
if (op->deassert_wp)
|
|
brcmnand_wp(mtd, 0);
|
|
|
|
- for (i = 0; i < op->ninstrs; i++) {
|
|
- ret = brcmnand_exec_instr(host, i, op);
|
|
- if (ret)
|
|
- break;
|
|
- }
|
|
+ ret = ctrl->exec_instr(chip, op);
|
|
|
|
if (op->deassert_wp)
|
|
brcmnand_wp(mtd, 1);
|
|
@@ -3130,6 +3329,15 @@ int brcmnand_probe(struct platform_devic
|
|
if (ret)
|
|
goto err;
|
|
|
|
+ /* Only v5.0+ controllers have low level ops support */
|
|
+ if (ctrl->nand_version >= 0x0500) {
|
|
+ ctrl->check_instr = brcmnand_check_instructions;
|
|
+ ctrl->exec_instr = brcmnand_exec_instructions;
|
|
+ } else {
|
|
+ ctrl->check_instr = brcmnand_check_instructions_legacy;
|
|
+ ctrl->exec_instr = brcmnand_exec_instructions_legacy;
|
|
+ }
|
|
+
|
|
/*
|
|
* Most chips have this cache at a fixed offset within 'nand' block.
|
|
* Some must specify this region separately.
|