[PATCH v3] mtd: rawnand: brcmnand: legacy exec_op implementation

David Regan dregan at broadcom.com
Thu May 15 16:59:51 PDT 2025


Hi Álvaro,

On Thu, May 15, 2025 at 12:52 AM Florian Fainelli
<florian.fainelli at broadcom.com> wrote:
>
>
>
> On 5/15/2025 7:06 AM, Álvaro Fernández Rojas wrote:
> > Hi William
> >
> > El jue, 15 may 2025 a las 3:42, William Zhang
> > (<william.zhang at broadcom.com>) escribió:
> >>
> >> Hi Alvaro,
> >>
> >>> -----Original Message-----
> >>> From: Álvaro Fernández Rojas <noltari at gmail.com>
> >>> Sent: Tuesday, May 13, 2025 11:24 PM
> >>> To: linux-mtd at lists.infradead.org; dregan at broadcom.com;
> >>> miquel.raynal at bootlin.com; bcm-kernel-feedback-list at broadcom.com;
> >>> florian.fainelli at broadcom.com; rafal at milecki.pl;
> >>> computersforpeace at gmail.com; kamal.dasu at broadcom.com;
> >>> dan.beygelman at broadcom.com; william.zhang at broadcom.com;
> >>> frieder.schrempf at kontron.de; linux-kernel at vger.kernel.org;
> >>> vigneshr at ti.com;
> >>> richard at nod.at; bbrezillon at kernel.org; kdasu.kdev at gmail.com;
> >>> jaimeliao.tw at gmail.com; kilobyte at angband.pl; jonas.gorski at gmail.com;
> >>> dgcbueu at gmail.com
> >>> Cc: Álvaro Fernández Rojas <noltari at gmail.com>
> >>> Subject: [PATCH v3] mtd: rawnand: brcmnand: legacy exec_op implementation
> >>>
> >>> 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 at gmail.com>
> >>> ---
> >>>   drivers/mtd/nand/raw/brcmnand/brcmnand.c | 178
> >>> ++++++++++++++++++++++-
> >>>   1 file changed, 172 insertions(+), 6 deletions(-)
> >>>
> >>>   v3: add changes requested by Florian and other improvements:
> >>>    - Add associative array for native command conversion.
> >>>    - Add function pointer to brcmnand_controller for exec_instr
> >>>      functionality.
> >>>    - Fix CMD_BLOCK_ERASE address.
> >>>    - Drop NAND_CMD_READOOB support.
> >>>
> >>>   v2: multiple improvements:
> >>>    - Use proper native commands for checks.
> >>>    - Fix NAND_CMD_PARAM/NAND_CMD_RNDOUT addr calculation.
> >>>    - Remove host->last_addr usage.
> >>>    - Remove sector_size_1k since it only applies to v5.0+ controllers.
> >>>    - Remove brcmnand_wp since it doesn't exist for < v5.0 controllers.
> >>>    - Use j instead of i for flash_cache loop.
> >>>
> >>> diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
> >>> b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
> >>> index 17f6d9723df9..f4fabe7ffd9d 100644
> >>> --- 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 */
> >>> +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,
> >> Do we not need to support nand_status_op()?
> >
> > We do, but NAND_CMD_STATUS and NAND_CMD_RESET are handled in brcmnand_exec_op():
> > https://github.com/torvalds/linux/blob/546bce579204685a0b204beebab98c3aa496e651/drivers/mtd/nand/raw/brcmnand/brcmnand.c#L2506-L2523
> >
> >
> >
> >>
> >>> +     [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,10 @@ struct brcmnand_controller {
> >>>        /* List of NAND hosts (one for each chip-select) */
> >>>        struct list_head host_list;
> >>>
> >>> +     /* Function to be called from exec_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;
> >>> @@ -2490,14 +2519,149 @@ static int brcmnand_op_is_reset(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_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);
> >>> +             } else {
> >>> +                     dev_err(ctrl->dev, "unsupported instruction type: %d\n",
> >>> instr->type);
> >>> +                     ret = -EINVAL;
> >>> +             }
> >>> +     }
> >>> +
> >>> +     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)
> >>> @@ -2525,11 +2689,7 @@ static int brcmnand_exec_op(struct nand_chip *chip,
> >>>        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;
> >>> -     }
> >>> +     ctrl->exec_instr(chip, op);
> >>>
> >>>        if (op->deassert_wp)
> >>>                brcmnand_wp(mtd, 1);
> >>> @@ -3142,6 +3302,12 @@ int brcmnand_probe(struct platform_device *pdev,
> >>> struct brcmnand_soc *soc)
> >>>        if (ret)
> >>>                goto err;
> >>>
> >>> +     /* Only v5.0+ controllers have low level ops support */
> >>> +     if (ctrl->nand_version >= 0x0500)

We can probably change this to >= 0x0400 since as Florian mentioned
LLOP was added
with version 4.

> >>> +             ctrl->exec_instr = brcmnand_exec_instructions;
> >>> +     else
> >>> +             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.
> >>> --
> >>> 2.39.5
> >
> > BTW, can anyone from Broadcom confirm any of the following?
> > 1. There are no low level registers on v2.1 and v2.2 controllers.
>
> Correct.
>
> > 2. Do low level registers exist on v4.0 controllers? They are defined
> > on 63268_map_part.h but the NAND drivers I could find never use them.
>
> They exist. The changelog for the NAND controller indicates that
> starting from v4.0 onwards, the NAND LL operation is supported.
>
> > 3. Are the low level registers bugged on v4.0 controllers?
> > 4. Should the low level registers be handled differently on v4.0 controllers?
> > The issue is that trying to use them on v4.0 controllers for
> > GET_FEATURES would leave the NAND controller in a weird state that
> > results in hangs or timeouts while trying to read the NAND.
> > This happens on the Sercomm H500-s, which is a BCM63268 with a
> > Macronix NAND that tries to unlock through ONFI.

I don't yet know the answer to this and not sure exactly which NAND
part you are using
but if you have just a regular standard NAND that doesn't do anything
unusual does that
work with a 63268 through the non-legacy brcmnand_exec_op
brcmnand_exec_instructions
path (>= 5) that you split out?

Does the data that comes back from LLOP operations look legitimate?

>
> That I do not know however, William and David hopefully do know.
> --
> Florian
>

-Dave



More information about the linux-mtd mailing list