summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilton D. Miller II <miltonm@us.ibm.com>2016-02-19 02:19:45 +0300
committerJoel Stanley <joel@jms.id.au>2016-02-22 08:27:33 +0300
commit908a99951b4882328a3cc4c2949683b1c9e99caa (patch)
tree7e61d870d3f7aad0cf61cd1eb2706d6ea37562ba
parent794044da20533e03259197e01fa4d6e415cbfbf6 (diff)
downloadlinux-openbmc-20160222-1.tar.xz
mtd: spi-nor: aspeed-smc: Use io string accessors to fifoopenbmc-20160222-1
Make and use new routines to access the fifo when in user mode. Instead of using memcpy_fromio, base the new routines on the io port string accessors. When the smc controllers are configured to transfer in user mode, the exact address within the chip select region is ignored and all valid data bytes are sent to the device fifo. It has been determined the reason _memcpy_fromio, an arm specific byte-by-byte variant of memcpy_fromio was required in read_reg, is that the later will stutter when coping from memory, discarding some bytes read, when switching from words to bytes for the final unaligned length. This change includes a hack to make inw/outw work. The default generic IO_SPACE_LIMIT is 0, and twenty bits when PCI is selected. While there is a PCMCIA SOC config to select the full 4GB space it would bring in the PCMCIA core for no use to the ast2400. We want to be part of the generic multi-platform arm kernel and therefore mach/io.h is not going to work. However, the limit is under an ifndef, so define it in this file an check it was not redefine at probe time. To avoid redefinition errors or warnings, guard the setting with CONFIG_ARM for cross-arch compile testing and with a Kconfig dependency for arm cross-compile tests. While some other architecture may not accept unsigned long addresses as ioport cookies, that can be addressed when, if ever, the hardware appears on another arch. While the change to use the designed-for-memory optimised copy template, even reusing the bytes of memcpy, was a recent change and only for little endian arm, designing and proposing a patch will take some time and there could be debate if the routine needs to work on changing memory. Signed-off-by: Milton Miller <miltonm@us.ibm.com> Reviewed-by: Andrew Jeffery <andrew@aj.id.au> Signed-off-by: Joel Stanley <joel@jms.id.au>
-rw-r--r--drivers/mtd/spi-nor/Kconfig2
-rw-r--r--drivers/mtd/spi-nor/aspeed-smc.c120
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;