summaryrefslogtreecommitdiff
path: root/drivers/mtd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/spi-nor/Kconfig11
-rw-r--r--drivers/mtd/spi-nor/Makefile1
-rw-r--r--drivers/mtd/spi-nor/aspeed-smc.c563
3 files changed, 575 insertions, 0 deletions
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 2fe2a7e90fa9..4a4465465d60 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -41,4 +41,15 @@ config SPI_NXP_SPIFI
Flash. Enable this option if you have a device with a SPIFI
controller and want to access the Flash as a mtd device.
+config ASPEED_FLASH_SPI
+ tristate "Aspeed flash controllers in SPI mode"
+ depends on HAS_IOMEM && OF
+ depends on ARCH_ASPEED || COMPILE_TEST
+ # IO_SPACE_LIMIT must be equivalent to (~0UL)
+ depends on !NEED_MACH_IO_H
+ help
+ This enables support for the New Static Memory Controller (FMC)
+ in the AST2400 when attached to SPI nor chips, and support for
+ the SPI Memory controller (SPI) for the BIOS.
+
endif # MTD_SPI_NOR
diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile
index e53333ef8582..ec286105333b 100644
--- a/drivers/mtd/spi-nor/Makefile
+++ b/drivers/mtd/spi-nor/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
+obj-$(CONFIG_ASPEED_FLASH_SPI) += aspeed-smc.o
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c
new file mode 100644
index 000000000000..ed55f283b619
--- /dev/null
+++ b/drivers/mtd/spi-nor/aspeed-smc.c
@@ -0,0 +1,563 @@
+/*
+ * ASPEED Static Memory Controller driver
+ * Copyright 2016 IBM Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+/* See comment by aspeed_smc_from_fifo */
+#ifdef CONFIG_ARM
+#define IO_SPACE_LIMIT (~0UL)
+#endif
+
+#include <linux/bug.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/spi-nor.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/sysfs.h>
+
+/*
+ * On the arm architecture, as of Linux version 4.3, memcpy_fromio
+ * stutters discarding some of the bytes read if the destination is
+ * unaligned, so we can't use it for reading from a fifo to a buffer
+ * of unknown alignment. Instead use the ins (l, w, b) family
+ * to read from the fifo. However, ARM tries to hide io port
+ * accesses from drivers unless there is a PCMCIA or PCI device, so
+ * we define the limit before all include files. There is a
+ * check in probe to make sure this will work, as long as the
+ * architecture uses an identity iomap.
+ */
+
+static void aspeed_smc_from_fifo(void *buf, const void __iomem *iop, size_t len)
+{
+ unsigned long io = (__force unsigned long)iop;
+
+ if (!len)
+ return;
+
+ /* Expect a 4 byte input port. Otherwise just read bytes */
+ if (unlikely(io & 3)) {
+ insb(io, buf, len);
+ return;
+ }
+
+ /* Align target to word: first byte then half word */
+ if ((unsigned long)buf & 1) {
+ *(u8 *)buf = inb(io);
+ buf++;
+ len--;
+ }
+ if (((unsigned long)buf & 2) && (len >= 2)) {
+ *(u16 *)buf = inw(io);
+ buf += 2;
+ len -= 2;
+ }
+ /* Transfer words, then remaining halfword and remaining byte */
+ if (len >= 4) {
+ insl(io, buf, len >> 2);
+ buf += len & ~3;
+ }
+ if (len & 2) {
+ *(u16 *)buf = inw(io);
+ buf += 2;
+ }
+ if (len & 1) {
+ *(u8 *)buf = inb(io);
+ }
+}
+
+static void aspeed_smc_to_fifo(void __iomem *iop, const void *buf, size_t len)
+{
+ unsigned long io = (__force unsigned long)iop;
+
+ if (!len)
+ return;
+
+ /* Expect a 4 byte output port. Otherwise just write bytes */
+ if (io & 3) {
+ outsb(io, buf, len);
+ return;
+ }
+
+ /* Align target to word: first byte then half word */
+ if ((unsigned long)buf & 1) {
+ outb(*(u8 *)buf, io);
+ buf++;
+ len--;
+ }
+ if (((unsigned long)buf & 2) && (len >= 2)) {
+ outw(*(u16 *)buf, io);
+ buf += 2;
+ len -= 2;
+ }
+ /* Transfer words, then remaining halfword and remaining byte */
+ if (len >= 4) {
+ outsl(io, buf, len >> 2);
+ buf += len & ~(size_t)3;
+ }
+ if (len & 2) {
+ outw(*(u16 *)buf, io);
+ buf += 2;
+ }
+ if (len & 1) {
+ outb(*(u8 *)buf, io);
+ }
+}
+
+enum smc_flash_type {
+ smc_type_nor = 0, /* controller connected to nor flash */
+ smc_type_nand = 1, /* controller connected to nand flash */
+ smc_type_spi = 2, /* controller connected to spi flash */
+};
+
+struct aspeed_smc_info {
+ u8 nce; /* number of chip enables */
+ u8 maxwidth; /* max width of spi bus */
+ bool hasdma; /* has dma engine */
+ bool hastype; /* type shift for ce 0 in cfg reg */
+ u8 we0; /* we shift for ce 0 in cfg reg */
+ u8 ctl0; /* offset in regs of ctl for ce 0 */
+ u8 cfg; /* offset in regs of cfg */
+ u8 time; /* offset in regs of timing */
+ u8 misc; /* offset in regs of misc settings */
+};
+
+static struct aspeed_smc_info fmc_info = {
+ .nce = 5,
+ .maxwidth = 4,
+ .hasdma = true,
+ .hastype = true,
+ .we0 = 16,
+ .ctl0 = 0x10,
+ .cfg = 0x00,
+ .time = 0x54,
+ .misc = 0x50,
+};
+
+static struct aspeed_smc_info smc_info = {
+ .nce = 1,
+ .maxwidth = 2,
+ .hasdma = false,
+ .hastype = false,
+ .we0 = 0,
+ .ctl0 = 0x04,
+ .cfg = 0x00,
+ .time = 0x14,
+ .misc = 0x10,
+};
+
+enum smc_ctl_reg_value {
+ smc_base, /* base value without mode for other commands */
+ smc_read, /* command reg for (maybe fast) reads */
+ smc_write, /* command reg for writes with timings */
+ smc_num_ctl_reg_values /* last value to get count of commands */
+};
+
+struct aspeed_smc_controller;
+
+struct aspeed_smc_chip {
+ struct aspeed_smc_controller *controller;
+ __le32 __iomem *ctl; /* control register */
+ void __iomem *base; /* base of chip window */
+ __le32 ctl_val[smc_num_ctl_reg_values]; /* controls with timing */
+ enum smc_flash_type type; /* what type of flash */
+ struct spi_nor nor;
+};
+
+struct aspeed_smc_controller {
+ struct mutex mutex; /* controller access mutex */
+ const struct aspeed_smc_info *info; /* type info of controller */
+ void __iomem *regs; /* controller registers */
+ struct aspeed_smc_chip *chips[0]; /* attached chips */
+};
+
+#define CONTROL_SPI_AAF_MODE BIT(31)
+#define CONTROL_SPI_IO_MODE_MASK GENMASK(30, 28)
+#define CONTROL_SPI_IO_DUAL_DATA BIT(29)
+#define CONTROL_SPI_IO_DUAL_ADDR_DATA (BIT(29) | BIT(28))
+#define CONTROL_SPI_IO_QUAD_DATA BIT(30)
+#define CONTROL_SPI_IO_QUAD_ADDR_DATA (BIT(30) | BIT(28))
+#define CONTROL_SPI_CE_INACTIVE_SHIFT 24
+#define CONTROL_SPI_CE_INACTIVE_MASK GENMASK(27, CONTROL_SPI_CE_INACTIVE_SHIFT)
+/* 0 = 16T ... 15 = 1T T=HCLK */
+#define CONTROL_SPI_COMMAND_SHIFT 16
+#define CONTROL_SPI_DUMMY_CYCLE_COMMAND_OUTPUT BIT(15)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_HI BIT(14)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_HI_SHIFT (14 - 2)
+#define CONTROL_SPI_IO_ADDRESS_4B BIT(13) /* FMC, LEGACY */
+#define CONTROL_SPI_CLK_DIV4 BIT(13) /* BIOS */
+#define CONTROL_SPI_RW_MERGE BIT(12)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT 6
+#define CONTROL_SPI_IO_DUMMY_CYCLES_LO GENMASK(7, CONTROL_SPI_IO_DUMMY_CYCLES_LO_SHIFT)
+#define CONTROL_SPI_IO_DUMMY_CYCLES_MASK (CONTROL_SPI_IO_DUMMY_CYCLES_HI | \
+ CONTROL_SPI_IO_DUMMY_CYCLES_LO)
+#define CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT 8
+#define CONTROL_SPI_CLOCK_FREQ_SEL_MASK GENMASK(11, CONTROL_SPI_CLOCK_FREQ_SEL_SHIFT)
+#define CONTROL_SPI_LSB_FIRST BIT(5)
+#define CONTROL_SPI_CLOCK_MODE_3 BIT(4)
+#define CONTROL_SPI_IN_DUAL_DATA BIT(3)
+#define CONTROL_SPI_CE_STOP_ACTIVE_CONTROL BIT(2)
+#define CONTROL_SPI_COMMAND_MODE_MASK GENMASK(1, 0)
+#define CONTROL_SPI_COMMAND_MODE_NORMAL (0)
+#define CONTROL_SPI_COMMAND_MODE_FREAD (1)
+#define CONTROL_SPI_COMMAND_MODE_WRITE (2)
+#define CONTROL_SPI_COMMAND_MODE_USER (3)
+
+#define CONTROL_SPI_KEEP_MASK (CONTROL_SPI_AAF_MODE | \
+ CONTROL_SPI_CE_INACTIVE_MASK | CONTROL_SPI_IO_ADDRESS_4B | \
+ CONTROL_SPI_IO_DUMMY_CYCLES_MASK | CONTROL_SPI_CLOCK_FREQ_SEL_MASK | \
+ CONTROL_SPI_LSB_FIRST | CONTROL_SPI_CLOCK_MODE_3)
+
+#define CONTROL_SPI_CLK_DIV4 BIT(13) /* BIOS */
+
+static u32 spi_control_fill_opcode(u8 opcode)
+{
+ return ((u32)(opcode)) << CONTROL_SPI_COMMAND_SHIFT;
+}
+
+static void aspeed_smc_start_user(struct spi_nor *nor)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+ u32 ctl = chip->ctl_val[smc_base];
+
+ mutex_lock(&chip->controller->mutex);
+
+ ctl |= CONTROL_SPI_COMMAND_MODE_USER |
+ CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
+ writel(ctl, chip->ctl);
+
+ ctl &= ~CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
+ writel(ctl, chip->ctl);
+}
+
+static void aspeed_smc_stop_user(struct spi_nor *nor)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ u32 ctl = chip->ctl_val[smc_read];
+ u32 ctl2 = ctl | CONTROL_SPI_COMMAND_MODE_USER |
+ CONTROL_SPI_CE_STOP_ACTIVE_CONTROL;
+
+ writel(ctl2, chip->ctl); /* stop user CE control */
+ writel(ctl, chip->ctl); /* default to fread or read */
+
+ mutex_unlock(&chip->controller->mutex);
+}
+
+static int aspeed_smc_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_to_fifo(chip->base, &opcode, 1);
+ aspeed_smc_from_fifo(buf, chip->base, len);
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
+ int len)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_to_fifo(chip->base, &opcode, 1);
+ aspeed_smc_to_fifo(chip->base, buf, len);
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+ __be32 temp;
+ u32 cmdaddr;
+
+ switch (nor->addr_width) {
+ default:
+ WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n",
+ nor->addr_width);
+ /* FALLTHROUGH */
+ case 3:
+ cmdaddr = addr & 0xFFFFFF;
+
+ cmdaddr |= (u32)cmd << 24;
+
+ temp = cpu_to_be32(cmdaddr);
+ aspeed_smc_to_fifo(chip->base, &temp, 4);
+ break;
+ case 4:
+ temp = cpu_to_be32(addr);
+ aspeed_smc_to_fifo(chip->base, &cmd, 1);
+ aspeed_smc_to_fifo(chip->base, &temp, 4);
+ break;
+ }
+}
+
+static int aspeed_smc_read_user(struct spi_nor *nor, loff_t from, size_t len,
+ size_t *retlen, u_char *read_buf)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from);
+ aspeed_smc_from_fifo(read_buf, chip->base, len);
+ *retlen += len;
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static void aspeed_smc_write_user(struct spi_nor *nor, loff_t to, size_t len,
+ size_t *retlen, const u_char *write_buf)
+{
+ struct aspeed_smc_chip *chip = nor->priv;
+
+ aspeed_smc_start_user(nor);
+ aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to);
+ aspeed_smc_to_fifo(chip->base, write_buf, len);
+ *retlen += len;
+ aspeed_smc_stop_user(nor);
+}
+
+static int aspeed_smc_erase(struct spi_nor *nor, loff_t offs)
+{
+ aspeed_smc_start_user(nor);
+ aspeed_smc_send_cmd_addr(nor, nor->erase_opcode, offs);
+ aspeed_smc_stop_user(nor);
+
+ return 0;
+}
+
+static int aspeed_smc_remove(struct platform_device *dev)
+{
+ struct aspeed_smc_chip *chip;
+ struct aspeed_smc_controller *controller = platform_get_drvdata(dev);
+ int n;
+
+ for (n = 0; n < controller->info->nce; n++) {
+ chip = controller->chips[n];
+ if (chip)
+ mtd_device_unregister(&chip->nor.mtd);
+ }
+
+ return 0;
+}
+
+const struct of_device_id aspeed_smc_matches[] = {
+ { .compatible = "aspeed,fmc", .data = &fmc_info },
+ { .compatible = "aspeed,smc", .data = &smc_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, aspeed_smc_matches);
+
+static struct platform_device *
+of_platform_device_create_or_find(struct device_node *child,
+ struct device *parent)
+{
+ struct platform_device *cdev;
+
+ cdev = of_platform_device_create(child, NULL, parent);
+ if (!cdev)
+ cdev = of_find_device_by_node(child);
+ return cdev;
+}
+
+static int aspeed_smc_probe(struct platform_device *dev)
+{
+ struct aspeed_smc_controller *controller;
+ const struct of_device_id *match;
+ const struct aspeed_smc_info *info;
+ struct resource *r;
+ void __iomem *regs;
+ struct device_node *child;
+ int err = 0;
+ unsigned int n;
+
+ /*
+ * This driver passes ioremap addresses to io port accessors.
+ * This works on arm if the IO_SPACE_LIMIT does not truncate
+ * the address.
+ */
+ if (~(unsigned long)IO_SPACE_LIMIT)
+ return -ENODEV;
+
+ match = of_match_device(aspeed_smc_matches, &dev->dev);
+ if (!match || !match->data)
+ return -ENODEV;
+ info = match->data;
+ r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ regs = devm_ioremap_resource(&dev->dev, r);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ controller = devm_kzalloc(&dev->dev, sizeof(*controller) +
+ info->nce * sizeof(controller->chips[0]), GFP_KERNEL);
+ if (!controller)
+ return -ENOMEM;
+ platform_set_drvdata(dev, controller);
+ controller->regs = regs;
+ controller->info = info;
+ mutex_init(&controller->mutex);
+
+ /* XXX turn off legacy mode if fmc ? */
+ /* XXX handshake to enable access to SMC (bios) controller w/ host? */
+
+ for_each_available_child_of_node(dev->dev.of_node, child) {
+ struct mtd_part_parser_data ppdata;
+ struct platform_device *cdev;
+ struct aspeed_smc_chip *chip;
+ u32 reg;
+
+ if (!of_device_is_compatible(child, "jedec,spi-nor"))
+ continue; /* XXX consider nand, nor children */
+
+
+ /*
+ * create a platform device from the of node.
+ * if the device already was created (eg from
+ * a prior bind/unbind cycle) use it
+ *
+ * XXX The child name will become the default mtd
+ * name in ioctl and /proc/mtd. Should we default
+ * to node->name (without unit)? The name must be
+ * unique among all platform devices. (Name would
+ * replace NULL in create call below).
+ * ... Or we can just encourage the label attribute.
+ *
+ * The only reason to do the child here is to use it in
+ * dev_err below for duplicate chip id. We could use
+ * the controller dev.
+ */
+ cdev = of_platform_device_create_or_find(child, &dev->dev);
+ if (!cdev)
+ continue;
+
+ err = of_property_read_u32(child, "reg", &n);
+ if (err == -EINVAL && info->nce == 1)
+ n = 0;
+ else if (err || n >= info->nce)
+ continue;
+ if (controller->chips[n]) {
+ dev_err(&cdev->dev,
+ "chip-id %u already in use in use by %s\n",
+ n, dev_name(controller->chips[n]->nor.dev));
+ continue;
+ }
+ chip = devm_kzalloc(&dev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ continue;
+
+ r = platform_get_resource(dev, IORESOURCE_MEM, n + 1);
+ chip->base = devm_ioremap_resource(&dev->dev, r);
+
+ if (!chip->base)
+ continue;
+ chip->controller = controller;
+ chip->ctl = controller->regs + info->ctl0 + n * 4;
+
+ /* dt said its spi. xxx Set it in controller if has_type */
+ chip->type = smc_type_spi;
+
+ /*
+ * Always turn on write enable bit in config register to
+ * allow opcodes to be sent in user mode.
+ */
+ mutex_lock(&controller->mutex);
+ reg = readl(controller->regs + info->cfg);
+ dev_dbg(&dev->dev, "flash config was %08x\n", reg);
+ reg |= 1 << (info->we0 + n); /* WEn */
+ writel(reg, controller->regs + info->cfg);
+ mutex_unlock(&controller->mutex);
+
+ /* XXX check resource within fmc CEx Segment Address Register */
+ /* XXX -- see dt vs jedec id vs bootloader */
+ /* XXX check / program clock phase/polarity, only 0 or 3 */
+
+ /*
+ * Read the existing control register to get basic values.
+ *
+ * XXX probably need more sanitation.
+ * XXX do we trust the bootloader or the device tree?
+ * spi-nor.c trusts jtag id over passed ids.
+ */
+ reg = readl(chip->ctl);
+ chip->ctl_val[smc_base] = reg & CONTROL_SPI_KEEP_MASK;
+
+ if ((reg & CONTROL_SPI_COMMAND_MODE_MASK) ==
+ CONTROL_SPI_COMMAND_MODE_NORMAL)
+ chip->ctl_val[smc_read] = reg;
+ else
+ chip->ctl_val[smc_read] = chip->ctl_val[smc_base] |
+ CONTROL_SPI_COMMAND_MODE_NORMAL;
+
+ chip->nor.dev = &cdev->dev;
+ chip->nor.priv = chip;
+ chip->nor.flash_node = child;
+ chip->nor.mtd.name = of_get_property(child, "label", NULL);
+ chip->nor.erase = aspeed_smc_erase;
+ chip->nor.read = aspeed_smc_read_user;
+ chip->nor.write = aspeed_smc_write_user;
+ chip->nor.read_reg = aspeed_smc_read_reg;
+ chip->nor.write_reg = aspeed_smc_write_reg;
+
+ /*
+ * XXX use of property and controller info width to choose
+ * SPI_NOR_QUAD , SPI_NOR_DUAL
+ */
+ err = spi_nor_scan(&chip->nor, NULL, SPI_NOR_NORMAL);
+ if (err)
+ continue;
+
+ chip->ctl_val[smc_write] = chip->ctl_val[smc_base] |
+ spi_control_fill_opcode(chip->nor.program_opcode) |
+ CONTROL_SPI_COMMAND_MODE_WRITE;
+
+ /* XXX intrepret nor flags into controller settings */
+ /* XXX enable fast read here */
+ /* XXX check if resource size big enough for chip */
+
+ memset(&ppdata, 0, sizeof(ppdata));
+ ppdata.of_node = cdev->dev.of_node;
+ err = mtd_device_parse_register(&chip->nor.mtd, NULL, &ppdata, NULL, 0);
+ if (err)
+ continue;
+ controller->chips[n] = chip;
+ }
+
+ /* did we register any children? */
+ for (n = 0; n < info->nce; n++)
+ if (controller->chips[n])
+ break;
+
+ if (n == info->nce)
+ return -ENODEV;
+
+ return 0;
+}
+
+static struct platform_driver aspeed_smc_driver = {
+ .probe = aspeed_smc_probe,
+ .remove = aspeed_smc_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = aspeed_smc_matches,
+ }
+};
+
+module_platform_driver(aspeed_smc_driver);
+
+MODULE_DESCRIPTION("ASPEED Static Memory Controller Driver");
+MODULE_AUTHOR("Milton Miller");
+MODULE_LICENSE("GPL v2");