| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright © 2008 Ilya Yanok, Emcraft Systems |
| */ |
| |
| #include <linux/slab.h> |
| #include <linux/module.h> |
| #include <linux/mtd/mtd.h> |
| #include <linux/mtd/rawnand.h> |
| #include <linux/mtd/partitions.h> |
| #include <linux/of_address.h> |
| #include <linux/of_platform.h> |
| #include <linux/io.h> |
| |
| #define FPGA_NAND_CMD_MASK (0x7 << 28) |
| #define FPGA_NAND_CMD_COMMAND (0x0 << 28) |
| #define FPGA_NAND_CMD_ADDR (0x1 << 28) |
| #define FPGA_NAND_CMD_READ (0x2 << 28) |
| #define FPGA_NAND_CMD_WRITE (0x3 << 28) |
| #define FPGA_NAND_BUSY (0x1 << 15) |
| #define FPGA_NAND_ENABLE (0x1 << 31) |
| #define FPGA_NAND_DATA_SHIFT 16 |
| |
| struct socrates_nand_host { |
| struct nand_controller controller; |
| struct nand_chip nand_chip; |
| void __iomem *io_base; |
| struct device *dev; |
| }; |
| |
| /** |
| * socrates_nand_write_buf - write buffer to chip |
| * @this: NAND chip object |
| * @buf: data buffer |
| * @len: number of bytes to write |
| */ |
| static void socrates_nand_write_buf(struct nand_chip *this, const uint8_t *buf, |
| int len) |
| { |
| int i; |
| struct socrates_nand_host *host = nand_get_controller_data(this); |
| |
| for (i = 0; i < len; i++) { |
| out_be32(host->io_base, FPGA_NAND_ENABLE | |
| FPGA_NAND_CMD_WRITE | |
| (buf[i] << FPGA_NAND_DATA_SHIFT)); |
| } |
| } |
| |
| /** |
| * socrates_nand_read_buf - read chip data into buffer |
| * @this: NAND chip object |
| * @buf: buffer to store date |
| * @len: number of bytes to read |
| */ |
| static void socrates_nand_read_buf(struct nand_chip *this, uint8_t *buf, |
| int len) |
| { |
| int i; |
| struct socrates_nand_host *host = nand_get_controller_data(this); |
| uint32_t val; |
| |
| val = FPGA_NAND_ENABLE | FPGA_NAND_CMD_READ; |
| |
| out_be32(host->io_base, val); |
| for (i = 0; i < len; i++) { |
| buf[i] = (in_be32(host->io_base) >> |
| FPGA_NAND_DATA_SHIFT) & 0xff; |
| } |
| } |
| |
| /** |
| * socrates_nand_read_byte - read one byte from the chip |
| * @mtd: MTD device structure |
| */ |
| static uint8_t socrates_nand_read_byte(struct nand_chip *this) |
| { |
| uint8_t byte; |
| socrates_nand_read_buf(this, &byte, sizeof(byte)); |
| return byte; |
| } |
| |
| /* |
| * Hardware specific access to control-lines |
| */ |
| static void socrates_nand_cmd_ctrl(struct nand_chip *nand_chip, int cmd, |
| unsigned int ctrl) |
| { |
| struct socrates_nand_host *host = nand_get_controller_data(nand_chip); |
| uint32_t val; |
| |
| if (cmd == NAND_CMD_NONE) |
| return; |
| |
| if (ctrl & NAND_CLE) |
| val = FPGA_NAND_CMD_COMMAND; |
| else |
| val = FPGA_NAND_CMD_ADDR; |
| |
| if (ctrl & NAND_NCE) |
| val |= FPGA_NAND_ENABLE; |
| |
| val |= (cmd & 0xff) << FPGA_NAND_DATA_SHIFT; |
| |
| out_be32(host->io_base, val); |
| } |
| |
| /* |
| * Read the Device Ready pin. |
| */ |
| static int socrates_nand_device_ready(struct nand_chip *nand_chip) |
| { |
| struct socrates_nand_host *host = nand_get_controller_data(nand_chip); |
| |
| if (in_be32(host->io_base) & FPGA_NAND_BUSY) |
| return 0; /* busy */ |
| return 1; |
| } |
| |
| static int socrates_attach_chip(struct nand_chip *chip) |
| { |
| if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && |
| chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) |
| chip->ecc.algo = NAND_ECC_ALGO_HAMMING; |
| |
| return 0; |
| } |
| |
| static const struct nand_controller_ops socrates_ops = { |
| .attach_chip = socrates_attach_chip, |
| }; |
| |
| /* |
| * Probe for the NAND device. |
| */ |
| static int socrates_nand_probe(struct platform_device *ofdev) |
| { |
| struct socrates_nand_host *host; |
| struct mtd_info *mtd; |
| struct nand_chip *nand_chip; |
| int res; |
| |
| /* Allocate memory for the device structure (and zero it) */ |
| host = devm_kzalloc(&ofdev->dev, sizeof(*host), GFP_KERNEL); |
| if (!host) |
| return -ENOMEM; |
| |
| host->io_base = of_iomap(ofdev->dev.of_node, 0); |
| if (host->io_base == NULL) { |
| dev_err(&ofdev->dev, "ioremap failed\n"); |
| return -EIO; |
| } |
| |
| nand_chip = &host->nand_chip; |
| mtd = nand_to_mtd(nand_chip); |
| host->dev = &ofdev->dev; |
| |
| nand_controller_init(&host->controller); |
| host->controller.ops = &socrates_ops; |
| nand_chip->controller = &host->controller; |
| |
| /* link the private data structures */ |
| nand_set_controller_data(nand_chip, host); |
| nand_set_flash_node(nand_chip, ofdev->dev.of_node); |
| mtd->name = "socrates_nand"; |
| mtd->dev.parent = &ofdev->dev; |
| |
| nand_chip->legacy.cmd_ctrl = socrates_nand_cmd_ctrl; |
| nand_chip->legacy.read_byte = socrates_nand_read_byte; |
| nand_chip->legacy.write_buf = socrates_nand_write_buf; |
| nand_chip->legacy.read_buf = socrates_nand_read_buf; |
| nand_chip->legacy.dev_ready = socrates_nand_device_ready; |
| |
| /* TODO: I have no idea what real delay is. */ |
| nand_chip->legacy.chip_delay = 20; /* 20us command delay time */ |
| |
| /* |
| * 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. |
| */ |
| nand_chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; |
| |
| dev_set_drvdata(&ofdev->dev, host); |
| |
| res = nand_scan(nand_chip, 1); |
| if (res) |
| goto out; |
| |
| res = mtd_device_register(mtd, NULL, 0); |
| if (!res) |
| return res; |
| |
| nand_cleanup(nand_chip); |
| |
| out: |
| iounmap(host->io_base); |
| return res; |
| } |
| |
| /* |
| * Remove a NAND device. |
| */ |
| static int socrates_nand_remove(struct platform_device *ofdev) |
| { |
| struct socrates_nand_host *host = dev_get_drvdata(&ofdev->dev); |
| struct nand_chip *chip = &host->nand_chip; |
| int ret; |
| |
| ret = mtd_device_unregister(nand_to_mtd(chip)); |
| WARN_ON(ret); |
| nand_cleanup(chip); |
| |
| iounmap(host->io_base); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id socrates_nand_match[] = |
| { |
| { |
| .compatible = "abb,socrates-nand", |
| }, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, socrates_nand_match); |
| |
| static struct platform_driver socrates_nand_driver = { |
| .driver = { |
| .name = "socrates_nand", |
| .of_match_table = socrates_nand_match, |
| }, |
| .probe = socrates_nand_probe, |
| .remove = socrates_nand_remove, |
| }; |
| |
| module_platform_driver(socrates_nand_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Ilya Yanok"); |
| MODULE_DESCRIPTION("NAND driver for Socrates board"); |