diff options
-rw-r--r-- | drivers/mtd/spi-nor/Kconfig | 2 | ||||
-rw-r--r-- | drivers/mtd/spi-nor/aspeed-smc.c | 120 |
2 files changed, 113 insertions, 9 deletions
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig index c7554d87a8a8..4a963c4eaf1b 100644 --- a/drivers/mtd/spi-nor/Kconfig +++ b/drivers/mtd/spi-nor/Kconfig @@ -44,6 +44,8 @@ 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 diff --git a/drivers/mtd/spi-nor/aspeed-smc.c b/drivers/mtd/spi-nor/aspeed-smc.c index 9dd46093af15..af8df0aa6dcc 100644 --- a/drivers/mtd/spi-nor/aspeed-smc.c +++ b/drivers/mtd/spi-nor/aspeed-smc.c @@ -8,6 +8,12 @@ * */ +/* 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> @@ -19,6 +25,94 @@ #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 */ @@ -165,8 +259,8 @@ 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); - writeb(opcode, chip->base); - _memcpy_fromio(buf, chip->base, len); + aspeed_smc_to_fifo(chip->base, &opcode, 1); + aspeed_smc_from_fifo(buf, chip->base, len); aspeed_smc_stop_user(nor); return 0; @@ -178,8 +272,8 @@ static int aspeed_smc_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, struct aspeed_smc_chip *chip = nor->priv; aspeed_smc_start_user(nor); - writeb(opcode, chip->base); - _memcpy_toio(chip->base, buf, len); + aspeed_smc_to_fifo(chip->base, &opcode, 1); + aspeed_smc_to_fifo(chip->base, buf, len); aspeed_smc_stop_user(nor); return 0; @@ -202,12 +296,12 @@ static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr) cmdaddr |= (u32)cmd << 24; temp = cpu_to_be32(cmdaddr); - memcpy_toio(chip->base, &temp, 4); + aspeed_smc_to_fifo(chip->base, &temp, 4); break; case 4: temp = cpu_to_be32(addr); - writeb(cmd, chip->base); - memcpy_toio(chip->base, &temp, 4); + aspeed_smc_to_fifo(chip->base, &cmd, 1); + aspeed_smc_to_fifo(chip->base, &temp, 4); break; } } @@ -219,7 +313,7 @@ static int aspeed_smc_read_user(struct spi_nor *nor, loff_t from, size_t len, aspeed_smc_start_user(nor); aspeed_smc_send_cmd_addr(nor, nor->read_opcode, from); - memcpy_fromio(read_buf, chip->base, len); + aspeed_smc_from_fifo(read_buf, chip->base, len); *retlen += len; aspeed_smc_stop_user(nor); @@ -233,7 +327,7 @@ static void aspeed_smc_write_user(struct spi_nor *nor, loff_t to, size_t len, aspeed_smc_start_user(nor); aspeed_smc_send_cmd_addr(nor, nor->program_opcode, to); - memcpy_toio(chip->base, write_buf, len); + aspeed_smc_to_fifo(chip->base, write_buf, len); *retlen += len; aspeed_smc_stop_user(nor); } @@ -292,6 +386,14 @@ static int aspeed_smc_probe(struct platform_device *dev) 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; |