18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Freescale UPM NAND driver. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright © 2007-2008 MontaVista Software, Inc. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Anton Vorontsov <avorontsov@ru.mvista.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/delay.h> 138c2ecf20Sopenharmony_ci#include <linux/mtd/rawnand.h> 148c2ecf20Sopenharmony_ci#include <linux/mtd/nand_ecc.h> 158c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h> 168c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 178c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 188c2ecf20Sopenharmony_ci#include <linux/io.h> 198c2ecf20Sopenharmony_ci#include <linux/slab.h> 208c2ecf20Sopenharmony_ci#include <asm/fsl_lbc.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistruct fsl_upm_nand { 238c2ecf20Sopenharmony_ci struct nand_controller base; 248c2ecf20Sopenharmony_ci struct device *dev; 258c2ecf20Sopenharmony_ci struct nand_chip chip; 268c2ecf20Sopenharmony_ci struct fsl_upm upm; 278c2ecf20Sopenharmony_ci uint8_t upm_addr_offset; 288c2ecf20Sopenharmony_ci uint8_t upm_cmd_offset; 298c2ecf20Sopenharmony_ci void __iomem *io_base; 308c2ecf20Sopenharmony_ci struct gpio_desc *rnb_gpio[NAND_MAX_CHIPS]; 318c2ecf20Sopenharmony_ci uint32_t mchip_offsets[NAND_MAX_CHIPS]; 328c2ecf20Sopenharmony_ci uint32_t mchip_count; 338c2ecf20Sopenharmony_ci uint32_t mchip_number; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic inline struct fsl_upm_nand *to_fsl_upm_nand(struct mtd_info *mtdinfo) 378c2ecf20Sopenharmony_ci{ 388c2ecf20Sopenharmony_ci return container_of(mtd_to_nand(mtdinfo), struct fsl_upm_nand, 398c2ecf20Sopenharmony_ci chip); 408c2ecf20Sopenharmony_ci} 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int fun_chip_init(struct fsl_upm_nand *fun, 438c2ecf20Sopenharmony_ci const struct device_node *upm_np, 448c2ecf20Sopenharmony_ci const struct resource *io_res) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(&fun->chip); 478c2ecf20Sopenharmony_ci int ret; 488c2ecf20Sopenharmony_ci struct device_node *flash_np; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci fun->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; 518c2ecf20Sopenharmony_ci fun->chip.ecc.algo = NAND_ECC_ALGO_HAMMING; 528c2ecf20Sopenharmony_ci fun->chip.controller = &fun->base; 538c2ecf20Sopenharmony_ci mtd->dev.parent = fun->dev; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci flash_np = of_get_next_child(upm_np, NULL); 568c2ecf20Sopenharmony_ci if (!flash_np) 578c2ecf20Sopenharmony_ci return -ENODEV; 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci nand_set_flash_node(&fun->chip, flash_np); 608c2ecf20Sopenharmony_ci mtd->name = devm_kasprintf(fun->dev, GFP_KERNEL, "0x%llx.%pOFn", 618c2ecf20Sopenharmony_ci (u64)io_res->start, 628c2ecf20Sopenharmony_ci flash_np); 638c2ecf20Sopenharmony_ci if (!mtd->name) { 648c2ecf20Sopenharmony_ci ret = -ENOMEM; 658c2ecf20Sopenharmony_ci goto err; 668c2ecf20Sopenharmony_ci } 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci ret = nand_scan(&fun->chip, fun->mchip_count); 698c2ecf20Sopenharmony_ci if (ret) 708c2ecf20Sopenharmony_ci goto err; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci ret = mtd_device_register(mtd, NULL, 0); 738c2ecf20Sopenharmony_cierr: 748c2ecf20Sopenharmony_ci of_node_put(flash_np); 758c2ecf20Sopenharmony_ci return ret; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_cistatic int func_exec_instr(struct nand_chip *chip, 798c2ecf20Sopenharmony_ci const struct nand_op_instr *instr) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci struct fsl_upm_nand *fun = to_fsl_upm_nand(nand_to_mtd(chip)); 828c2ecf20Sopenharmony_ci u32 mar, reg_offs = fun->mchip_offsets[fun->mchip_number]; 838c2ecf20Sopenharmony_ci unsigned int i; 848c2ecf20Sopenharmony_ci const u8 *out; 858c2ecf20Sopenharmony_ci u8 *in; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci switch (instr->type) { 888c2ecf20Sopenharmony_ci case NAND_OP_CMD_INSTR: 898c2ecf20Sopenharmony_ci fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset); 908c2ecf20Sopenharmony_ci mar = (instr->ctx.cmd.opcode << (32 - fun->upm.width)) | 918c2ecf20Sopenharmony_ci reg_offs; 928c2ecf20Sopenharmony_ci fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar); 938c2ecf20Sopenharmony_ci fsl_upm_end_pattern(&fun->upm); 948c2ecf20Sopenharmony_ci return 0; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci case NAND_OP_ADDR_INSTR: 978c2ecf20Sopenharmony_ci fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset); 988c2ecf20Sopenharmony_ci for (i = 0; i < instr->ctx.addr.naddrs; i++) { 998c2ecf20Sopenharmony_ci mar = (instr->ctx.addr.addrs[i] << (32 - fun->upm.width)) | 1008c2ecf20Sopenharmony_ci reg_offs; 1018c2ecf20Sopenharmony_ci fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar); 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci fsl_upm_end_pattern(&fun->upm); 1048c2ecf20Sopenharmony_ci return 0; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci case NAND_OP_DATA_IN_INSTR: 1078c2ecf20Sopenharmony_ci in = instr->ctx.data.buf.in; 1088c2ecf20Sopenharmony_ci for (i = 0; i < instr->ctx.data.len; i++) 1098c2ecf20Sopenharmony_ci in[i] = in_8(fun->io_base + reg_offs); 1108c2ecf20Sopenharmony_ci return 0; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci case NAND_OP_DATA_OUT_INSTR: 1138c2ecf20Sopenharmony_ci out = instr->ctx.data.buf.out; 1148c2ecf20Sopenharmony_ci for (i = 0; i < instr->ctx.data.len; i++) 1158c2ecf20Sopenharmony_ci out_8(fun->io_base + reg_offs, out[i]); 1168c2ecf20Sopenharmony_ci return 0; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci case NAND_OP_WAITRDY_INSTR: 1198c2ecf20Sopenharmony_ci if (!fun->rnb_gpio[fun->mchip_number]) 1208c2ecf20Sopenharmony_ci return nand_soft_waitrdy(chip, instr->ctx.waitrdy.timeout_ms); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci return nand_gpio_waitrdy(chip, fun->rnb_gpio[fun->mchip_number], 1238c2ecf20Sopenharmony_ci instr->ctx.waitrdy.timeout_ms); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci default: 1268c2ecf20Sopenharmony_ci return -EINVAL; 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int fun_exec_op(struct nand_chip *chip, const struct nand_operation *op, 1338c2ecf20Sopenharmony_ci bool check_only) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct fsl_upm_nand *fun = to_fsl_upm_nand(nand_to_mtd(chip)); 1368c2ecf20Sopenharmony_ci unsigned int i; 1378c2ecf20Sopenharmony_ci int ret; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci if (op->cs >= NAND_MAX_CHIPS) 1408c2ecf20Sopenharmony_ci return -EINVAL; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci if (check_only) 1438c2ecf20Sopenharmony_ci return 0; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci fun->mchip_number = op->cs; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci for (i = 0; i < op->ninstrs; i++) { 1488c2ecf20Sopenharmony_ci ret = func_exec_instr(chip, &op->instrs[i]); 1498c2ecf20Sopenharmony_ci if (ret) 1508c2ecf20Sopenharmony_ci return ret; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci if (op->instrs[i].delay_ns) 1538c2ecf20Sopenharmony_ci ndelay(op->instrs[i].delay_ns); 1548c2ecf20Sopenharmony_ci } 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci return 0; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic const struct nand_controller_ops fun_ops = { 1608c2ecf20Sopenharmony_ci .exec_op = fun_exec_op, 1618c2ecf20Sopenharmony_ci}; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic int fun_probe(struct platform_device *ofdev) 1648c2ecf20Sopenharmony_ci{ 1658c2ecf20Sopenharmony_ci struct fsl_upm_nand *fun; 1668c2ecf20Sopenharmony_ci struct resource *io_res; 1678c2ecf20Sopenharmony_ci const __be32 *prop; 1688c2ecf20Sopenharmony_ci int ret; 1698c2ecf20Sopenharmony_ci int size; 1708c2ecf20Sopenharmony_ci int i; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci fun = devm_kzalloc(&ofdev->dev, sizeof(*fun), GFP_KERNEL); 1738c2ecf20Sopenharmony_ci if (!fun) 1748c2ecf20Sopenharmony_ci return -ENOMEM; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci io_res = platform_get_resource(ofdev, IORESOURCE_MEM, 0); 1778c2ecf20Sopenharmony_ci fun->io_base = devm_ioremap_resource(&ofdev->dev, io_res); 1788c2ecf20Sopenharmony_ci if (IS_ERR(fun->io_base)) 1798c2ecf20Sopenharmony_ci return PTR_ERR(fun->io_base); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci ret = fsl_upm_find(io_res->start, &fun->upm); 1828c2ecf20Sopenharmony_ci if (ret) { 1838c2ecf20Sopenharmony_ci dev_err(&ofdev->dev, "can't find UPM\n"); 1848c2ecf20Sopenharmony_ci return ret; 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci prop = of_get_property(ofdev->dev.of_node, "fsl,upm-addr-offset", 1888c2ecf20Sopenharmony_ci &size); 1898c2ecf20Sopenharmony_ci if (!prop || size != sizeof(uint32_t)) { 1908c2ecf20Sopenharmony_ci dev_err(&ofdev->dev, "can't get UPM address offset\n"); 1918c2ecf20Sopenharmony_ci return -EINVAL; 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci fun->upm_addr_offset = *prop; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci prop = of_get_property(ofdev->dev.of_node, "fsl,upm-cmd-offset", &size); 1968c2ecf20Sopenharmony_ci if (!prop || size != sizeof(uint32_t)) { 1978c2ecf20Sopenharmony_ci dev_err(&ofdev->dev, "can't get UPM command offset\n"); 1988c2ecf20Sopenharmony_ci return -EINVAL; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci fun->upm_cmd_offset = *prop; 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci prop = of_get_property(ofdev->dev.of_node, 2038c2ecf20Sopenharmony_ci "fsl,upm-addr-line-cs-offsets", &size); 2048c2ecf20Sopenharmony_ci if (prop && (size / sizeof(uint32_t)) > 0) { 2058c2ecf20Sopenharmony_ci fun->mchip_count = size / sizeof(uint32_t); 2068c2ecf20Sopenharmony_ci if (fun->mchip_count >= NAND_MAX_CHIPS) { 2078c2ecf20Sopenharmony_ci dev_err(&ofdev->dev, "too much multiple chips\n"); 2088c2ecf20Sopenharmony_ci return -EINVAL; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci for (i = 0; i < fun->mchip_count; i++) 2118c2ecf20Sopenharmony_ci fun->mchip_offsets[i] = be32_to_cpu(prop[i]); 2128c2ecf20Sopenharmony_ci } else { 2138c2ecf20Sopenharmony_ci fun->mchip_count = 1; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci for (i = 0; i < fun->mchip_count; i++) { 2178c2ecf20Sopenharmony_ci fun->rnb_gpio[i] = devm_gpiod_get_index_optional(&ofdev->dev, 2188c2ecf20Sopenharmony_ci NULL, i, 2198c2ecf20Sopenharmony_ci GPIOD_IN); 2208c2ecf20Sopenharmony_ci if (IS_ERR(fun->rnb_gpio[i])) { 2218c2ecf20Sopenharmony_ci dev_err(&ofdev->dev, "RNB gpio #%d is invalid\n", i); 2228c2ecf20Sopenharmony_ci return PTR_ERR(fun->rnb_gpio[i]); 2238c2ecf20Sopenharmony_ci } 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci nand_controller_init(&fun->base); 2278c2ecf20Sopenharmony_ci fun->base.ops = &fun_ops; 2288c2ecf20Sopenharmony_ci fun->dev = &ofdev->dev; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci ret = fun_chip_init(fun, ofdev->dev.of_node, io_res); 2318c2ecf20Sopenharmony_ci if (ret) 2328c2ecf20Sopenharmony_ci return ret; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci dev_set_drvdata(&ofdev->dev, fun); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci return 0; 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic int fun_remove(struct platform_device *ofdev) 2408c2ecf20Sopenharmony_ci{ 2418c2ecf20Sopenharmony_ci struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev); 2428c2ecf20Sopenharmony_ci struct nand_chip *chip = &fun->chip; 2438c2ecf20Sopenharmony_ci struct mtd_info *mtd = nand_to_mtd(chip); 2448c2ecf20Sopenharmony_ci int ret; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci ret = mtd_device_unregister(mtd); 2478c2ecf20Sopenharmony_ci WARN_ON(ret); 2488c2ecf20Sopenharmony_ci nand_cleanup(chip); 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci return 0; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic const struct of_device_id of_fun_match[] = { 2548c2ecf20Sopenharmony_ci { .compatible = "fsl,upm-nand" }, 2558c2ecf20Sopenharmony_ci {}, 2568c2ecf20Sopenharmony_ci}; 2578c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, of_fun_match); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic struct platform_driver of_fun_driver = { 2608c2ecf20Sopenharmony_ci .driver = { 2618c2ecf20Sopenharmony_ci .name = "fsl,upm-nand", 2628c2ecf20Sopenharmony_ci .of_match_table = of_fun_match, 2638c2ecf20Sopenharmony_ci }, 2648c2ecf20Sopenharmony_ci .probe = fun_probe, 2658c2ecf20Sopenharmony_ci .remove = fun_remove, 2668c2ecf20Sopenharmony_ci}; 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cimodule_platform_driver(of_fun_driver); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2718c2ecf20Sopenharmony_ciMODULE_AUTHOR("Anton Vorontsov <avorontsov@ru.mvista.com>"); 2728c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Driver for NAND chips working through Freescale " 2738c2ecf20Sopenharmony_ci "LocalBus User-Programmable Machine"); 274