// SPDX-License-Identifier: GPL-2.0 /* * Technologic Systems TS72xx NAND controller driver * * Copyright (C) 2023 Nikita Shubin * * Derived from: plat_nand.c * Author: Vitaly Wool */ #include #include #include #include #include #include #include #include #include #define TS72XX_NAND_CONTROL_ADDR_LINE BIT(22) /* 0xN0400000 */ #define TS72XX_NAND_BUSY_ADDR_LINE BIT(23) /* 0xN0800000 */ #define TS72XX_NAND_ALE BIT(0) #define TS72XX_NAND_CLE BIT(1) #define TS72XX_NAND_NCE BIT(2) #define TS72XX_NAND_CTRL_CLE (TS72XX_NAND_NCE | TS72XX_NAND_CLE) #define TS72XX_NAND_CTRL_ALE (TS72XX_NAND_NCE | TS72XX_NAND_ALE) struct ts72xx_nand_data { struct nand_controller controller; struct nand_chip chip; void __iomem *base; void __iomem *ctrl; void __iomem *busy; }; static inline struct ts72xx_nand_data *chip_to_ts72xx(struct nand_chip *chip) { return container_of(chip, struct ts72xx_nand_data, chip); } static int ts72xx_nand_attach_chip(struct nand_chip *chip) { switch (chip->ecc.engine_type) { case NAND_ECC_ENGINE_TYPE_ON_HOST: return -EINVAL; case NAND_ECC_ENGINE_TYPE_SOFT: if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) chip->ecc.algo = NAND_ECC_ALGO_HAMMING; chip->ecc.algo = NAND_ECC_ALGO_HAMMING; fallthrough; default: return 0; } } static void ts72xx_nand_ctrl(struct nand_chip *chip, u8 value) { struct ts72xx_nand_data *data = chip_to_ts72xx(chip); unsigned char bits = ioread8(data->ctrl) & ~GENMASK(2, 0); iowrite8(bits | value, data->ctrl); } static int ts72xx_nand_exec_instr(struct nand_chip *chip, const struct nand_op_instr *instr) { struct ts72xx_nand_data *data = chip_to_ts72xx(chip); unsigned int timeout_us; u32 status; int ret; switch (instr->type) { case NAND_OP_CMD_INSTR: ts72xx_nand_ctrl(chip, TS72XX_NAND_CTRL_CLE); iowrite8(instr->ctx.cmd.opcode, data->base); ts72xx_nand_ctrl(chip, TS72XX_NAND_NCE); break; case NAND_OP_ADDR_INSTR: ts72xx_nand_ctrl(chip, TS72XX_NAND_CTRL_ALE); iowrite8_rep(data->base, instr->ctx.addr.addrs, instr->ctx.addr.naddrs); ts72xx_nand_ctrl(chip, TS72XX_NAND_NCE); break; case NAND_OP_DATA_IN_INSTR: ioread8_rep(data->base, instr->ctx.data.buf.in, instr->ctx.data.len); break; case NAND_OP_DATA_OUT_INSTR: iowrite8_rep(data->base, instr->ctx.data.buf.in, instr->ctx.data.len); break; case NAND_OP_WAITRDY_INSTR: timeout_us = instr->ctx.waitrdy.timeout_ms * 1000; ret = readb_poll_timeout(data->busy, status, status & BIT(5), 0, timeout_us); if (ret) return ret; break; } if (instr->delay_ns) ndelay(instr->delay_ns); return 0; } static int ts72xx_nand_exec_op(struct nand_chip *chip, const struct nand_operation *op, bool check_only) { unsigned int i; int ret; if (check_only) return 0; for (i = 0; i < op->ninstrs; i++) { ret = ts72xx_nand_exec_instr(chip, &op->instrs[i]); if (ret) return ret; } return 0; } static const struct nand_controller_ops ts72xx_nand_ops = { .attach_chip = ts72xx_nand_attach_chip, .exec_op = ts72xx_nand_exec_op, }; static int ts72xx_nand_probe(struct platform_device *pdev) { struct ts72xx_nand_data *data; struct fwnode_handle *child; struct mtd_info *mtd; int err; data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; nand_controller_init(&data->controller); data->controller.ops = &ts72xx_nand_ops; data->chip.controller = &data->controller; data->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(data->base)) return PTR_ERR(data->base); data->ctrl = data->base + TS72XX_NAND_CONTROL_ADDR_LINE; data->busy = data->base + TS72XX_NAND_BUSY_ADDR_LINE; child = fwnode_get_next_child_node(dev_fwnode(&pdev->dev), NULL); if (!child) return dev_err_probe(&pdev->dev, -ENXIO, "ts72xx controller node should have exactly one child\n"); nand_set_flash_node(&data->chip, to_of_node(child)); mtd = nand_to_mtd(&data->chip); mtd->dev.parent = &pdev->dev; platform_set_drvdata(pdev, data); /* * This driver assumes that the default ECC engine should be TYPE_SOFT. * Set ->engine_type before registering the NAND devices in order to * provide a driver specific default value. */ data->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; /* Scan to find existence of the device */ err = nand_scan(&data->chip, 1); if (err) goto err_handle_put; err = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0); if (err) goto err_clean_nand; return 0; err_clean_nand: nand_cleanup(&data->chip); err_handle_put: fwnode_handle_put(child); return err; } static void ts72xx_nand_remove(struct platform_device *pdev) { struct ts72xx_nand_data *data = platform_get_drvdata(pdev); struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); struct nand_chip *chip = &data->chip; int ret; ret = mtd_device_unregister(nand_to_mtd(chip)); WARN_ON(ret); nand_cleanup(chip); fwnode_handle_put(fwnode); } static const struct of_device_id ts72xx_id_table[] = { { .compatible = "technologic,ts7200-nand" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ts72xx_id_table); static struct platform_driver ts72xx_nand_driver = { .driver = { .name = "ts72xx-nand", .of_match_table = ts72xx_id_table, }, .probe = ts72xx_nand_probe, .remove = ts72xx_nand_remove, }; module_platform_driver(ts72xx_nand_driver); MODULE_AUTHOR("Nikita Shubin "); MODULE_DESCRIPTION("Technologic Systems TS72xx NAND controller driver"); MODULE_LICENSE("GPL");